/* * Copyright (C) 2008-2009 SVOX AG, Baslerstr. 30, 8048 Zuerich, Switzerland * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ #include "svox_ssml_parser.h" #include <utils/Log.h> #include <cutils/jstring.h> #include <string.h> #include <utils/String16.h> #define SSML_PITCH_XLOW "50" #define SSML_PITCH_LOW "75" #define SSML_PITCH_MEDIUM "100" #define SSML_PITCH_HIGH "150" #define SSML_PITCH_XHIGH "200" #define SSML_RATE_XSLOW "30" #define SSML_RATE_SLOW "60" #define SSML_RATE_MEDIUM "100" #define SSML_RATE_FAST "250" #define SSML_RATE_XFAST "500" #define SSML_VOLUME_SILENT "0" #define SSML_VOLUME_XLOW "25" #define SSML_VOLUME_LOW "70" #define SSML_VOLUME_MEDIUM "120" #define SSML_VOLUME_LOUD "300" #define SSML_VOLUME_XLOUD "450" #define SSML_BREAK_NONE "0ms" #define SSML_BREAK_XWEAK "100ms" #define SSML_BREAK_WEAK "300ms" #define SSML_BREAK_MEDIUM "600ms" #define SSML_BREAK_STRONG "1s" #define SSML_BREAK_XSTRONG "3s" extern int cnvIpaToXsampa(const char16_t* ipaString, size_t ipaStringSize, char** outXsampaString); extern char * createPhonemeString( const char * xsampa, int length ); SvoxSsmlParser::SvoxSsmlParser() : m_isInBreak(0), m_appendix(NULL), m_docLanguage(NULL) { mParser = XML_ParserCreate("UTF-8"); if (mParser) { XML_SetElementHandler(mParser, starttagHandler, endtagHandler); XML_SetCharacterDataHandler(mParser, textHandler); XML_SetUserData(mParser, (void*)this); m_datasize = 512; m_data = new char[m_datasize]; m_data[0] = '\0'; } } SvoxSsmlParser::~SvoxSsmlParser() { if (mParser) XML_ParserFree(mParser); if (m_data) delete [] m_data; if (m_appendix) delete [] m_appendix; if (m_docLanguage) delete [] m_docLanguage; } int SvoxSsmlParser::initSuccessful() { return (mParser && m_data); } int SvoxSsmlParser::parseDocument(const char* ssmldoc, int isFinal) { int doclen = (int)strlen(ssmldoc) + 1; int status = XML_Parse(mParser, ssmldoc, doclen, isFinal); if (status == XML_STATUS_ERROR) { /* Note: for some reason Expat almost always complains about invalid tokens, even when document is well formed */ ALOGI("Parser error at line %d: %s\n", (int)XML_GetCurrentLineNumber(mParser), XML_ErrorString(XML_GetErrorCode(mParser))); } return status; } char* SvoxSsmlParser::getParsedDocument() { return m_data; } char* SvoxSsmlParser::getParsedDocumentLanguage() { return m_docLanguage; } void SvoxSsmlParser::starttagHandler(void* data, const XML_Char* element, const XML_Char** attributes) { ((SvoxSsmlParser*)data)->startElement(element, attributes); } void SvoxSsmlParser::startElement(const XML_Char* element, const XML_Char** attributes) { if (strcmp(element, "speak") == 0) { if (strlen(m_data) > 0) { /* we have old data, get rid of it and reallocate memory */ delete m_data; m_data = NULL; m_datasize = 512; m_data = new char[m_datasize]; if (!m_data) { ALOGE("Error: failed to allocate memory for string!\n"); return; } } /* the only attribute supported in the speak tag is xml:lang, all others are ignored */ for (int i = 0; attributes[i]; i += 2) { if (strcmp(attributes[i], "xml:lang") == 0) { if (!m_docLanguage) { m_docLanguage = new char[strlen(attributes[i+1])+1]; } strcpy(m_docLanguage, attributes[i+1]); break; } } } else if (strcmp(element, "p") == 0) /* currently no attributes are supported for <p> */ { if (strlen(m_data) + 4 > (size_t)m_datasize) { if (!growDataSize(100)) { ALOGE("Error: failed to allocate memory for string!\n"); return; } } strcat(m_data, "<p>"); } else if (strcmp(element, "s") == 0) /* currently no attributes are supported for <s> */ { if (strlen(m_data) + 4 > (size_t)m_datasize) { if (!growDataSize(100)) { ALOGE("Error: failed to allocate memory for string!\n"); return; } } strcat(m_data, "<s>"); } else if (strcmp(element, "phoneme") == 0) /* only ipa and xsampa alphabets are supported */ { int alpha = 1; /* set to 1 if alphabet is ipa */ int tagComplete = 0; /* set to 1 if phoneme tag has already been added */ char16_t* ph = NULL; char* xsampastr = NULL; size_t phsize = 0; size_t xsampasize = 0; for (int i = 0; attributes[i]; i += 2) { if (strcmp(attributes[i], "alphabet") == 0) { if (strcmp(attributes[i+1], "xsampa") == 0) { alpha = 0; } } if (strcmp(attributes[i], "ph") == 0) { ph = new char16_t[strlen8to16(attributes[i+1]) + 1]; ph = strdup8to16(attributes[i+1], &phsize); } } if (!ph) { /* error, no phonetic string */ ALOGE("Error: bad SSML syntax, ph attribute not supplied."); return; } if (alpha) { /* need to convert phoneme string to xsampa */ xsampasize = cnvIpaToXsampa(ph, phsize, &xsampastr); delete [] ph; if (!xsampastr) { ALOGE("Error: failed to allocate memory for IPA string conversion"); return; } } else { xsampastr = strndup16to8(ph, phsize); xsampasize = strlen(xsampastr); delete [] ph; } /* split XSAMPA string into multiple phonemes if needed */ if (strstr(xsampastr, " ") || strstr(xsampastr, "#")) /* check again to see if we have multiple words */ { char* phonstr = createPhonemeString(xsampastr, strlen(xsampastr) + 1); free(xsampastr); xsampastr = NULL; xsampastr = (char*)malloc(strlen(phonstr) + 1); strcpy(xsampastr, phonstr); free(phonstr); phonstr = NULL; tagComplete = 1; } if (tagComplete) { if (strlen(m_data) + strlen(xsampastr) + 1 > (size_t)m_datasize) { if (!growDataSize(100)) { ALOGE("Error: failed to allocate memory for string!"); free(xsampastr); return; } } } else { if (strlen(m_data) + strlen(xsampastr) + 17 > (size_t)m_datasize) { if (!growDataSize(100)) { ALOGE("Error: failed to allocate memory for string!"); free(xsampastr); return; } } strcat(m_data, "<phoneme ph='"); } strcat(m_data, xsampastr); free(xsampastr); if (!tagComplete) { if (strlen(m_data) + 4 > (size_t)m_datasize) { if (!growDataSize(100)) { ALOGE("Error: failed to allocate memory for string!\n"); return; } } strcat(m_data, "'/>"); } m_isInBreak = 1; /* set flag to indicate any text between open and close tag is to be discarded */ } else if (strcmp(element, "break") == 0) { if (strlen(m_data) + 17 > (size_t)m_datasize) { if (!growDataSize(100)) { ALOGE("Error: failed to allocate memory for string!\n"); return; } } strcat(m_data, "<break time='"); char* time = NULL; for (int i = 0; attributes[i]; i += 2) { if (strcmp(attributes[i], "time") == 0) { time = new char[strlen(attributes[i+1]) + 1]; if (!time) { ALOGE("Error: failed to allocate memory for string!\n"); return; } strcpy(time, attributes[i+1]); } else if (strcmp(attributes[i], "strength") == 0 && !time) { time = convertBreakStrengthToTime(attributes[i+1]); } } if (!time) { time = new char[6]; if (!time) { ALOGE("Error: failed to allocate memory for string!\n"); return; } strcpy(time, SSML_BREAK_WEAK); /* if no time or strength attributes are specified, default to weak break */ } if (strlen(m_data) + strlen(time) + 4 > (size_t)m_datasize) { if (!growDataSize(100)) { ALOGE("Error: failed to allocate memory for string!\n"); return; } } strcat(m_data, time); strcat(m_data, "'/>"); m_isInBreak = 1; /* set flag to indicate any text between open and close tag is to be discarded */ } else if (strcmp(element, "prosody") == 0) /* only pitch, rate and volume attributes are supported */ { for (int i = 0; attributes[i]; i += 2) { if (strcmp(attributes[i], "pitch") == 0) { char* svoxpitch = convertToSvoxPitch(attributes[i+1]); if (!svoxpitch) { ALOGE("Error: failed to allocate memory for string!\n"); return; } if (!svoxpitch) { svoxpitch = new char[4]; if (!svoxpitch) { ALOGE("Error: failed to allocate memory for string!\n"); return; } strcpy(svoxpitch, "100"); } char* pitch = new char[17 + strlen(svoxpitch)]; if (!pitch) { ALOGE("Error: failed to allocate memory for string!\n"); return; } sprintf(pitch, "<pitch level='%s'>", svoxpitch); if (strlen(m_data) + strlen(pitch) + 1 > (size_t)m_datasize) { if (!growDataSize(100)) { ALOGE("Error: failed to allocate memory for string!\n"); return; } } strcat(m_data, pitch); if (!m_appendix) { m_appendix = new char[30]; m_appendix[0] = '\0'; } strcat(m_appendix, "</pitch>"); delete [] svoxpitch; delete [] pitch; } else if (strcmp(attributes[i], "rate") == 0) { char* svoxrate = convertToSvoxRate(attributes[i+1]); if (!svoxrate) { svoxrate = new char[4]; if (!svoxrate) { ALOGE("Error: failed to allocate memory for string!\n"); return; } strcpy(svoxrate, "100"); } char* rate = new char[17 + strlen(svoxrate)]; if (!rate) { ALOGE("Error: failed to allocate memory for string!\n"); return; } sprintf(rate, "<speed level='%s'>", svoxrate); if (strlen(m_data) + strlen(rate) + 1 > (size_t)m_datasize) { if (!growDataSize(100)) { ALOGE("Error: failed to allocate memory for string!\n"); return; } } strcat(m_data, rate); if (!m_appendix) { m_appendix = new char[30]; if (!m_appendix) { ALOGE("Error: failed to allocate memory for string!\n"); return; } m_appendix[0] = '\0'; } strcat(m_appendix, "</speed>"); delete [] svoxrate; delete [] rate; } else if (strcmp(attributes[i], "volume") == 0) { char* svoxvol = convertToSvoxVolume(attributes[i+1]); if (!svoxvol) { svoxvol = new char[4]; if (!svoxvol) { ALOGE("Error: failed to allocate memory for string!\n"); return; } strcpy(svoxvol, "100"); } char* volume = new char[18 + strlen(svoxvol)]; if (!volume) { ALOGE("Error: failed to allocate memory for string!\n"); return; } sprintf(volume, "<volume level='%s'>", svoxvol); if (strlen(m_data) + strlen(volume) + 1 > (size_t)m_datasize) { if (!growDataSize(100)) { ALOGE("Error: failed to allocate memory for string!\n"); return; } } strcat(m_data, volume); if (!m_appendix) { m_appendix = new char[30]; m_appendix[0] = '\0'; } strcat(m_appendix, "</volume>"); delete [] svoxvol; delete [] volume; } } } else if (strcmp(element, "audio") == 0) /* only 16kHz 16bit wav files are supported as src */ { if (strlen(m_data) + 17 > (size_t)m_datasize) { if (!growDataSize(100)) { ALOGE("Error: failed to allocate memory for string!\n"); return; } } strcat(m_data, "<usesig file='"); for (int i = 0; attributes[i]; i += 2) { if (strcmp(attributes[i], "src") == 0) { if (strlen(m_data) + strlen(attributes[i+1]) + 1 > (size_t)m_datasize) { if (!growDataSize(100)) { ALOGE("Error: failed to allocate memory for string!\n"); return; } } strcat(m_data, attributes[i+1]); } } strcat(m_data, "'>"); } } void SvoxSsmlParser::endtagHandler(void* data, const XML_Char* element) { ((SvoxSsmlParser*)data)->endElement(element); } void SvoxSsmlParser::endElement(const XML_Char* element) { if (strcmp(element, "speak") == 0) { /* do nothing */ } else if (strcmp(element, "p") == 0) { if (strlen(m_data) + 5 > (size_t)m_datasize) { if (!growDataSize(100)) { ALOGE("Error: failed to allocate memory for string!\n"); return; } } strcat(m_data, "</p>"); } else if (strcmp(element, "s") == 0) { if (strlen(m_data) + 5 > (size_t)m_datasize) { if (!growDataSize(100)) { ALOGE("Error: failed to allocate memory for string!\n"); return; } } strcat(m_data, "</s>"); } else if (strcmp(element, "phoneme") == 0) { m_isInBreak = 0; /* indicate we are no longer in phoneme tag */ } else if (strcmp(element, "break") == 0) { m_isInBreak = 0; /* indicate we are no longer in break tag */ } else if (strcmp(element, "prosody") == 0) { if (m_appendix) { if (strlen(m_data) + strlen(m_appendix) + 1 > (size_t)m_datasize) { if (!growDataSize(100)) { ALOGE("Error: failed to allocate memory for string!\n"); return; } } strcat(m_data, m_appendix); delete [] m_appendix; m_appendix = NULL; } } else if (strcmp(element, "audio") == 0) { if (strlen(m_data) + 10 > (size_t)m_datasize) { if (!growDataSize(100)) { ALOGE("Error: failed to allocate memory for string!\n"); return; } } strcat(m_data, "</usesig>"); } } void SvoxSsmlParser::textHandler(void* data, const XML_Char* text, int length) { ((SvoxSsmlParser*)data)->textElement(text, length); } void SvoxSsmlParser::textElement(const XML_Char* text, int length) { if (m_isInBreak) { return; /* handles the case when someone has added text inside the break or phoneme tag - this text is thrown away */ } char* content = new char[length + 1]; if (!content) { ALOGE("Error: failed to allocate memory for string!\n"); return; } strncpy(content, text, length); content[length] = '\0'; if (strlen(m_data) + strlen(content) + 1 > (size_t)m_datasize) { if (!growDataSize(100)) { ALOGE("Error: failed to allocate memory for string!\n"); return; } } strcat(m_data, content); delete [] content; } /** convertToSvoxPitch Converts SSML pitch labels to SVOX pitch levels */ char* SvoxSsmlParser::convertToSvoxPitch(const char* value) { char* converted = NULL; if (strcmp(value, "x-low") == 0) { converted = new char[4]; if (!converted) { ALOGE("Error: failed to allocate memory for string!\n"); return NULL; } strcpy(converted, SSML_PITCH_XLOW); } else if (strcmp(value, "low") == 0) { converted = new char[4]; if (!converted) { ALOGE("Error: failed to allocate memory for string!\n"); return NULL; } strcpy(converted, SSML_PITCH_LOW); } else if (strcmp(value, "medium") == 0) { converted = new char[4]; if (!converted) { ALOGE("Error: failed to allocate memory for string!\n"); return NULL; } strcpy(converted, SSML_PITCH_MEDIUM); } else if (strcmp(value, "default") == 0) { converted = new char[4]; if (!converted) { ALOGE("Error: failed to allocate memory for string!\n"); return NULL; } strcpy(converted, SSML_PITCH_MEDIUM); } else if (strcmp(value, "high") == 0) { converted = new char[4]; if (!converted) { ALOGE("Error: failed to allocate memory for string!\n"); return NULL; } strcpy(converted, SSML_PITCH_HIGH); } else if (strcmp(value, "x-high") == 0) { converted = new char[4]; if (!converted) { ALOGE("Error: failed to allocate memory for string!\n"); return NULL; } strcpy(converted, SSML_PITCH_XHIGH); } return converted; } /** convertToSvoxRate Converts SSML rate labels to SVOX speed levels */ char* SvoxSsmlParser::convertToSvoxRate(const char* value) { char* converted = NULL; if (strcmp(value, "x-slow") == 0) { converted = new char[4]; if (!converted) { ALOGE("Error: failed to allocate memory for string!\n"); return NULL; } strcpy(converted, SSML_RATE_XSLOW); } else if (strcmp(value, "slow") == 0) { converted = new char[4]; if (!converted) { ALOGE("Error: failed to allocate memory for string!\n"); return NULL; } strcpy(converted, SSML_RATE_SLOW); } else if (strcmp(value, "medium") == 0) { converted = new char[4]; if (!converted) { ALOGE("Error: failed to allocate memory for string!\n"); return NULL; } strcpy(converted, SSML_RATE_MEDIUM); } else if (strcmp(value, "default") == 0) { converted = new char[4]; if (!converted) { ALOGE("Error: failed to allocate memory for string!\n"); return NULL; } strcpy(converted, SSML_RATE_MEDIUM); } else if (strcmp(value, "fast") == 0) { converted = new char[4]; if (!converted) { ALOGE("Error: failed to allocate memory for string!\n"); return NULL; } strcpy(converted, SSML_RATE_FAST); } else if (strcmp(value, "x-fast") == 0) { converted = new char[4]; if (!converted) { ALOGE("Error: failed to allocate memory for string!\n"); return NULL; } strcpy(converted, SSML_RATE_XFAST); } return converted; } /** convertToSvoxVolume Converts SSML volume labels to SVOX volume levels */ char* SvoxSsmlParser::convertToSvoxVolume(const char* value) { char* converted = NULL; if (strcmp(value, "silent") == 0) { converted = new char[4]; if (!converted) { ALOGE("Error: failed to allocate memory for string!\n"); return NULL; } strcpy(converted, SSML_VOLUME_SILENT); } else if (strcmp(value, "x-low") == 0) { converted = new char[4]; if (!converted) { ALOGE("Error: failed to allocate memory for string!\n"); return NULL; } strcpy(converted, SSML_VOLUME_XLOW); } else if (strcmp(value, "low") == 0) { converted = new char[4]; if (!converted) { ALOGE("Error: failed to allocate memory for string!\n"); return NULL; } strcpy(converted, SSML_VOLUME_LOW); } else if (strcmp(value, "medium") == 0) { converted = new char[4]; if (!converted) { ALOGE("Error: failed to allocate memory for string!\n"); return NULL; } strcpy(converted, SSML_VOLUME_MEDIUM); } else if (strcmp(value, "default") == 0) { converted = new char[4]; if (!converted) { ALOGE("Error: failed to allocate memory for string!\n"); return NULL; } strcpy(converted, SSML_VOLUME_MEDIUM); } else if (strcmp(value, "loud") == 0) { converted = new char[4]; if (!converted) { ALOGE("Error: failed to allocate memory for string!\n"); return NULL; } strcpy(converted, SSML_VOLUME_LOUD); } else if (strcmp(value, "x-loud") == 0) { converted = new char[4]; if (!converted) { ALOGE("Error: failed to allocate memory for string!\n"); return NULL; } strcpy(converted, SSML_VOLUME_XLOUD); } return converted; } /** convertBreakStrengthToTime Converts SSML break strength labels to SVOX break time */ char* SvoxSsmlParser::convertBreakStrengthToTime(const char* value) { char* converted = NULL; if (strcmp(value, "none") == 0) { converted = new char[6]; if (!converted) { ALOGE("Error: failed to allocate memory for string!\n"); return NULL; } strcpy(converted, SSML_BREAK_NONE); } else if (strcmp(value, "x-weak") == 0) { converted = new char[6]; if (!converted) { ALOGE("Error: failed to allocate memory for string!\n"); return NULL; } strcpy(converted, SSML_BREAK_XWEAK); } else if (strcmp(value, "weak") == 0) { converted = new char[6]; if (!converted) { ALOGE("Error: failed to allocate memory for string!\n"); return NULL; } strcpy(converted, SSML_BREAK_WEAK); } else if (strcmp(value, "medium") == 0) { converted = new char[6]; if (!converted) { ALOGE("Error: failed to allocate memory for string!\n"); return NULL; } strcpy(converted, SSML_BREAK_MEDIUM); } else if (strcmp(value, "strong") == 0) { converted = new char[6]; if (!converted) { ALOGE("Error: failed to allocate memory for string!\n"); return NULL; } strcpy(converted, SSML_BREAK_STRONG); } else if (strcmp(value, "x-strong") == 0) { converted = new char[6]; if (!converted) { ALOGE("Error: failed to allocate memory for string!\n"); return NULL; } strcpy(converted, SSML_BREAK_XSTRONG); } return converted; } /** growDataSize Increases the size of the internal text storage member */ int SvoxSsmlParser::growDataSize(int sizeToGrow) { char* tmp = new char[m_datasize]; if (!tmp) return 0; strcpy(tmp, m_data); delete [] m_data; m_data = NULL; m_data = new char[m_datasize + sizeToGrow]; if (!m_data) { m_data = tmp; return 0; } m_datasize += sizeToGrow; strcpy(m_data, tmp); delete [] tmp; tmp = NULL; return 1; }