1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 //#define LOG_NDEBUG 0 18 #define LOG_TAG "TimedTextSRTSource" 19 #include <utils/Log.h> 20 21 #include <binder/Parcel.h> 22 #include <media/stagefright/foundation/ADebug.h> // for CHECK_xx 23 #include <media/stagefright/foundation/AString.h> 24 #include <media/stagefright/DataSource.h> 25 #include <media/stagefright/MediaDefs.h> // for MEDIA_MIMETYPE_xxx 26 #include <media/stagefright/MediaErrors.h> 27 #include <media/stagefright/MediaSource.h> 28 #include <media/stagefright/MetaData.h> 29 30 #include "TimedTextSRTSource.h" 31 #include "TextDescriptions.h" 32 33 namespace android { 34 35 TimedTextSRTSource::TimedTextSRTSource(const sp<DataSource>& dataSource) 36 : mSource(dataSource), 37 mMetaData(new MetaData), 38 mIndex(0) { 39 } 40 41 TimedTextSRTSource::~TimedTextSRTSource() { 42 } 43 44 status_t TimedTextSRTSource::start() { 45 status_t err = scanFile(); 46 if (err != OK) { 47 reset(); 48 } 49 // TODO: Need to detect the language, because SRT doesn't give language 50 // information explicitly. 51 mMetaData->setCString(kKeyMediaLanguage, ""); 52 return err; 53 } 54 55 void TimedTextSRTSource::reset() { 56 mMetaData->clear(); 57 mTextVector.clear(); 58 mIndex = 0; 59 } 60 61 status_t TimedTextSRTSource::stop() { 62 reset(); 63 return OK; 64 } 65 66 status_t TimedTextSRTSource::read( 67 int64_t *startTimeUs, 68 int64_t *endTimeUs, 69 Parcel *parcel, 70 const MediaSource::ReadOptions *options) { 71 AString text; 72 status_t err = getText(options, &text, startTimeUs, endTimeUs); 73 if (err != OK) { 74 return err; 75 } 76 77 CHECK_GE(*startTimeUs, 0); 78 extractAndAppendLocalDescriptions(*startTimeUs, text, parcel); 79 return OK; 80 } 81 82 status_t TimedTextSRTSource::scanFile() { 83 off64_t offset = 0; 84 int64_t startTimeUs; 85 bool endOfFile = false; 86 87 while (!endOfFile) { 88 TextInfo info; 89 status_t err = getNextSubtitleInfo(&offset, &startTimeUs, &info); 90 switch (err) { 91 case OK: 92 mTextVector.add(startTimeUs, info); 93 break; 94 case ERROR_END_OF_STREAM: 95 endOfFile = true; 96 break; 97 default: 98 return err; 99 } 100 } 101 if (mTextVector.isEmpty()) { 102 return ERROR_MALFORMED; 103 } 104 return OK; 105 } 106 107 /* SRT format: 108 * Subtitle number 109 * Start time --> End time 110 * Text of subtitle (one or more lines) 111 * Blank lines 112 * 113 * .srt file example: 114 * 1 115 * 00:00:20,000 --> 00:00:24,400 116 * Altocumulus clouds occr between six thousand 117 * 118 * 2 119 * 00:00:24,600 --> 00:00:27,800 120 * and twenty thousand feet above ground level. 121 */ 122 status_t TimedTextSRTSource::getNextSubtitleInfo( 123 off64_t *offset, int64_t *startTimeUs, TextInfo *info) { 124 AString data; 125 status_t err; 126 127 // To skip blank lines. 128 do { 129 if ((err = readNextLine(offset, &data)) != OK) { 130 return err; 131 } 132 data.trim(); 133 } while (data.empty()); 134 135 // Just ignore the first non-blank line which is subtitle sequence number. 136 if ((err = readNextLine(offset, &data)) != OK) { 137 return err; 138 } 139 int hour1, hour2, min1, min2, sec1, sec2, msec1, msec2; 140 // the start time format is: hours:minutes:seconds,milliseconds 141 // 00:00:24,600 --> 00:00:27,800 142 if (sscanf(data.c_str(), "%02d:%02d:%02d,%03d --> %02d:%02d:%02d,%03d", 143 &hour1, &min1, &sec1, &msec1, &hour2, &min2, &sec2, &msec2) != 8) { 144 return ERROR_MALFORMED; 145 } 146 147 *startTimeUs = ((hour1 * 3600 + min1 * 60 + sec1) * 1000 + msec1) * 1000ll; 148 info->endTimeUs = ((hour2 * 3600 + min2 * 60 + sec2) * 1000 + msec2) * 1000ll; 149 if (info->endTimeUs <= *startTimeUs) { 150 return ERROR_MALFORMED; 151 } 152 153 info->offset = *offset; 154 bool needMoreData = true; 155 while (needMoreData) { 156 if ((err = readNextLine(offset, &data)) != OK) { 157 if (err == ERROR_END_OF_STREAM) { 158 needMoreData = false; 159 } else { 160 return err; 161 } 162 } 163 164 if (needMoreData) { 165 data.trim(); 166 if (data.empty()) { 167 // it's an empty line used to separate two subtitles 168 needMoreData = false; 169 } 170 } 171 } 172 info->textLen = *offset - info->offset; 173 return OK; 174 } 175 176 status_t TimedTextSRTSource::readNextLine(off64_t *offset, AString *data) { 177 data->clear(); 178 while (true) { 179 ssize_t readSize; 180 char character; 181 if ((readSize = mSource->readAt(*offset, &character, 1)) < 1) { 182 if (readSize == 0) { 183 return ERROR_END_OF_STREAM; 184 } 185 return ERROR_IO; 186 } 187 188 (*offset)++; 189 190 // a line could end with CR, LF or CR + LF 191 if (character == 10) { 192 break; 193 } else if (character == 13) { 194 if ((readSize = mSource->readAt(*offset, &character, 1)) < 1) { 195 if (readSize == 0) { // end of the stream 196 return OK; 197 } 198 return ERROR_IO; 199 } 200 201 (*offset)++; 202 if (character != 10) { 203 (*offset)--; 204 } 205 break; 206 } 207 data->append(character); 208 } 209 return OK; 210 } 211 212 status_t TimedTextSRTSource::getText( 213 const MediaSource::ReadOptions *options, 214 AString *text, int64_t *startTimeUs, int64_t *endTimeUs) { 215 if (mTextVector.size() == 0) { 216 return ERROR_END_OF_STREAM; 217 } 218 text->clear(); 219 int64_t seekTimeUs; 220 MediaSource::ReadOptions::SeekMode mode; 221 if (options != NULL && options->getSeekTo(&seekTimeUs, &mode)) { 222 int64_t lastEndTimeUs = 223 mTextVector.valueAt(mTextVector.size() - 1).endTimeUs; 224 int64_t firstStartTimeUs = mTextVector.keyAt(0); 225 if (seekTimeUs < 0 || seekTimeUs > lastEndTimeUs) { 226 return ERROR_OUT_OF_RANGE; 227 } else if (seekTimeUs < firstStartTimeUs) { 228 mIndex = 0; 229 } else { 230 // binary search 231 size_t low = 0; 232 size_t high = mTextVector.size() - 1; 233 size_t mid = 0; 234 int64_t currTimeUs; 235 236 while (low <= high) { 237 mid = low + (high - low)/2; 238 currTimeUs = mTextVector.keyAt(mid); 239 const int64_t diffTime = currTimeUs - seekTimeUs; 240 241 if (diffTime == 0) { 242 break; 243 } else if (diffTime < 0) { 244 low = mid + 1; 245 } else { 246 if ((high == mid + 1) 247 && (seekTimeUs < mTextVector.keyAt(high))) { 248 break; 249 } 250 if (mid < 1) { 251 break; 252 } 253 high = mid - 1; 254 } 255 } 256 mIndex = mid; 257 } 258 } 259 260 if (mIndex >= mTextVector.size()) { 261 return ERROR_END_OF_STREAM; 262 } 263 const TextInfo &info = mTextVector.valueAt(mIndex); 264 *startTimeUs = mTextVector.keyAt(mIndex); 265 *endTimeUs = info.endTimeUs; 266 mIndex++; 267 268 char *str = new char[info.textLen]; 269 if (mSource->readAt(info.offset, str, info.textLen) < info.textLen) { 270 delete[] str; 271 return ERROR_IO; 272 } 273 text->append(str, info.textLen); 274 delete[] str; 275 return OK; 276 } 277 278 status_t TimedTextSRTSource::extractAndAppendLocalDescriptions( 279 int64_t timeUs, const AString &text, Parcel *parcel) { 280 const void *data = text.c_str(); 281 size_t size = text.size(); 282 int32_t flag = TextDescriptions::LOCAL_DESCRIPTIONS | 283 TextDescriptions::OUT_OF_BAND_TEXT_SRT; 284 285 if (size > 0) { 286 return TextDescriptions::getParcelOfDescriptions( 287 (const uint8_t *)data, size, flag, timeUs / 1000, parcel); 288 } 289 return OK; 290 } 291 292 sp<MetaData> TimedTextSRTSource::getFormat() { 293 return mMetaData; 294 } 295 296 } // namespace android 297