TimedTextSRTSource.cpp revision f9d660a5e0196240add5daf0199f128d471e592c
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/AString.h> 23#include <media/stagefright/DataSource.h> 24#include <media/stagefright/MediaDefs.h> // for MEDIA_MIMETYPE_xxx 25#include <media/stagefright/MediaErrors.h> 26#include <media/stagefright/MediaSource.h> 27#include <media/stagefright/MetaData.h> 28 29#include "TimedTextSRTSource.h" 30#include "TextDescriptions.h" 31 32namespace android { 33 34TimedTextSRTSource::TimedTextSRTSource(const sp<DataSource>& dataSource) 35 : mSource(dataSource), 36 mMetaData(new MetaData), 37 mIndex(0) { 38} 39 40TimedTextSRTSource::~TimedTextSRTSource() { 41} 42 43status_t TimedTextSRTSource::start() { 44 status_t err = scanFile(); 45 if (err != OK) { 46 reset(); 47 } 48 // TODO: Need to detect the language, because SRT doesn't give language 49 // information explicitly. 50 mMetaData->setCString(kKeyMediaLanguage, ""); 51 return err; 52} 53 54void TimedTextSRTSource::reset() { 55 mMetaData->clear(); 56 mTextVector.clear(); 57 mIndex = 0; 58} 59 60status_t TimedTextSRTSource::stop() { 61 reset(); 62 return OK; 63} 64 65status_t TimedTextSRTSource::read( 66 int64_t *timeUs, 67 Parcel *parcel, 68 const MediaSource::ReadOptions *options) { 69 int64_t endTimeUs; 70 AString text; 71 status_t err = getText(options, &text, timeUs, &endTimeUs); 72 if (err != OK) { 73 return err; 74 } 75 76 if (*timeUs > 0) { 77 extractAndAppendLocalDescriptions(*timeUs, text, parcel); 78 } 79 return OK; 80} 81 82status_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 */ 122status_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 176status_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 212status_t TimedTextSRTSource::getText( 213 const MediaSource::ReadOptions *options, 214 AString *text, int64_t *startTimeUs, int64_t *endTimeUs) { 215 text->clear(); 216 int64_t seekTimeUs; 217 MediaSource::ReadOptions::SeekMode mode; 218 if (options != NULL && options->getSeekTo(&seekTimeUs, &mode)) { 219 int64_t lastEndTimeUs = 220 mTextVector.valueAt(mTextVector.size() - 1).endTimeUs; 221 int64_t firstStartTimeUs = mTextVector.keyAt(0); 222 if (seekTimeUs < 0 || seekTimeUs > lastEndTimeUs) { 223 return ERROR_OUT_OF_RANGE; 224 } else if (seekTimeUs < firstStartTimeUs) { 225 mIndex = 0; 226 } else { 227 // binary search 228 ssize_t low = 0; 229 ssize_t high = mTextVector.size() - 1; 230 ssize_t mid = 0; 231 int64_t currTimeUs; 232 233 while (low <= high) { 234 mid = low + (high - low)/2; 235 currTimeUs = mTextVector.keyAt(mid); 236 const int diff = currTimeUs - seekTimeUs; 237 238 if (diff == 0) { 239 break; 240 } else if (diff < 0) { 241 low = mid + 1; 242 } else { 243 if ((high == mid + 1) 244 && (seekTimeUs < mTextVector.keyAt(high))) { 245 break; 246 } 247 high = mid - 1; 248 } 249 } 250 mIndex = mid; 251 } 252 } 253 const TextInfo &info = mTextVector.valueAt(mIndex); 254 *startTimeUs = mTextVector.keyAt(mIndex); 255 *endTimeUs = info.endTimeUs; 256 mIndex++; 257 258 char *str = new char[info.textLen]; 259 if (mSource->readAt(info.offset, str, info.textLen) < info.textLen) { 260 delete[] str; 261 return ERROR_IO; 262 } 263 text->append(str, info.textLen); 264 delete[] str; 265 return OK; 266} 267 268status_t TimedTextSRTSource::extractAndAppendLocalDescriptions( 269 int64_t timeUs, const AString &text, Parcel *parcel) { 270 const void *data = text.c_str(); 271 size_t size = text.size(); 272 int32_t flag = TextDescriptions::LOCAL_DESCRIPTIONS | 273 TextDescriptions::OUT_OF_BAND_TEXT_SRT; 274 275 if (size > 0) { 276 return TextDescriptions::getParcelOfDescriptions( 277 (const uint8_t *)data, size, flag, timeUs / 1000, parcel); 278 } 279 return OK; 280} 281 282sp<MetaData> TimedTextSRTSource::getFormat() { 283 return mMetaData; 284} 285 286} // namespace android 287