Home | History | Annotate | Download | only in timedtext
      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 sp<MetaData> TimedTextSRTSource::getFormat() {
     83     return mMetaData;
     84 }
     85 
     86 status_t TimedTextSRTSource::scanFile() {
     87     off64_t offset = 0;
     88     int64_t startTimeUs;
     89     bool endOfFile = false;
     90 
     91     while (!endOfFile) {
     92         TextInfo info;
     93         status_t err = getNextSubtitleInfo(&offset, &startTimeUs, &info);
     94         switch (err) {
     95             case OK:
     96                 mTextVector.add(startTimeUs, info);
     97                 break;
     98             case ERROR_END_OF_STREAM:
     99                 endOfFile = true;
    100                 break;
    101             default:
    102                 return err;
    103         }
    104     }
    105     if (mTextVector.isEmpty()) {
    106         return ERROR_MALFORMED;
    107     }
    108     return OK;
    109 }
    110 
    111 /* SRT format:
    112  *   Subtitle number
    113  *   Start time --> End time
    114  *   Text of subtitle (one or more lines)
    115  *   Blank lines
    116  *
    117  * .srt file example:
    118  * 1
    119  * 00:00:20,000 --> 00:00:24,400
    120  * Altocumulus clouds occr between six thousand
    121  *
    122  * 2
    123  * 00:00:24,600 --> 00:00:27,800
    124  * and twenty thousand feet above ground level.
    125  */
    126 status_t TimedTextSRTSource::getNextSubtitleInfo(
    127           off64_t *offset, int64_t *startTimeUs, TextInfo *info) {
    128     AString data;
    129     status_t err;
    130 
    131     // To skip blank lines.
    132     do {
    133         if ((err = readNextLine(offset, &data)) != OK) {
    134             return err;
    135         }
    136         data.trim();
    137     } while (data.empty());
    138 
    139     // Just ignore the first non-blank line which is subtitle sequence number.
    140     if ((err = readNextLine(offset, &data)) != OK) {
    141         return err;
    142     }
    143     int hour1, hour2, min1, min2, sec1, sec2, msec1, msec2;
    144     // the start time format is: hours:minutes:seconds,milliseconds
    145     // 00:00:24,600 --> 00:00:27,800
    146     if (sscanf(data.c_str(), "%02d:%02d:%02d,%03d --> %02d:%02d:%02d,%03d",
    147                &hour1, &min1, &sec1, &msec1, &hour2, &min2, &sec2, &msec2) != 8) {
    148         return ERROR_MALFORMED;
    149     }
    150 
    151     *startTimeUs = ((hour1 * 3600 + min1 * 60 + sec1) * 1000 + msec1) * 1000ll;
    152     info->endTimeUs = ((hour2 * 3600 + min2 * 60 + sec2) * 1000 + msec2) * 1000ll;
    153     if (info->endTimeUs <= *startTimeUs) {
    154         return ERROR_MALFORMED;
    155     }
    156 
    157     info->offset = *offset;
    158     bool needMoreData = true;
    159     while (needMoreData) {
    160         if ((err = readNextLine(offset, &data)) != OK) {
    161             if (err == ERROR_END_OF_STREAM) {
    162                 break;
    163             } else {
    164                 return err;
    165             }
    166         }
    167 
    168         data.trim();
    169         if (data.empty()) {
    170             // it's an empty line used to separate two subtitles
    171             needMoreData = false;
    172         }
    173     }
    174     info->textLen = *offset - info->offset;
    175     return OK;
    176 }
    177 
    178 status_t TimedTextSRTSource::readNextLine(off64_t *offset, AString *data) {
    179     data->clear();
    180     while (true) {
    181         ssize_t readSize;
    182         char character;
    183         if ((readSize = mSource->readAt(*offset, &character, 1)) < 1) {
    184             if (readSize == 0) {
    185                 return ERROR_END_OF_STREAM;
    186             }
    187             return ERROR_IO;
    188         }
    189 
    190         (*offset)++;
    191 
    192         // a line could end with CR, LF or CR + LF
    193         if (character == 10) {
    194             break;
    195         } else if (character == 13) {
    196             if ((readSize = mSource->readAt(*offset, &character, 1)) < 1) {
    197                 if (readSize == 0) {  // end of the stream
    198                     return OK;
    199                 }
    200                 return ERROR_IO;
    201             }
    202 
    203             (*offset)++;
    204             if (character != 10) {
    205                 (*offset)--;
    206             }
    207             break;
    208         }
    209         data->append(character);
    210     }
    211     return OK;
    212 }
    213 
    214 status_t TimedTextSRTSource::getText(
    215         const MediaSource::ReadOptions *options,
    216         AString *text, int64_t *startTimeUs, int64_t *endTimeUs) {
    217     if (mTextVector.size() == 0) {
    218         return ERROR_END_OF_STREAM;
    219     }
    220     text->clear();
    221     int64_t seekTimeUs;
    222     MediaSource::ReadOptions::SeekMode mode;
    223     if (options != NULL && options->getSeekTo(&seekTimeUs, &mode)) {
    224         int64_t lastEndTimeUs =
    225                 mTextVector.valueAt(mTextVector.size() - 1).endTimeUs;
    226         if (seekTimeUs < 0) {
    227             return ERROR_OUT_OF_RANGE;
    228         } else if (seekTimeUs >= lastEndTimeUs) {
    229             return ERROR_END_OF_STREAM;
    230         } else {
    231             // binary search
    232             size_t low = 0;
    233             size_t high = mTextVector.size() - 1;
    234             size_t mid = 0;
    235 
    236             while (low <= high) {
    237                 mid = low + (high - low)/2;
    238                 int diff = compareExtendedRangeAndTime(mid, seekTimeUs);
    239                 if (diff == 0) {
    240                     break;
    241                 } else if (diff < 0) {
    242                     low = mid + 1;
    243                 } else {
    244                     high = mid - 1;
    245                 }
    246             }
    247             mIndex = mid;
    248         }
    249     }
    250 
    251     if (mIndex >= mTextVector.size()) {
    252         return ERROR_END_OF_STREAM;
    253     }
    254 
    255     const TextInfo &info = mTextVector.valueAt(mIndex);
    256     *startTimeUs = mTextVector.keyAt(mIndex);
    257     *endTimeUs = info.endTimeUs;
    258     mIndex++;
    259 
    260     char *str = new char[info.textLen];
    261     if (mSource->readAt(info.offset, str, info.textLen) < info.textLen) {
    262         delete[] str;
    263         return ERROR_IO;
    264     }
    265     text->append(str, info.textLen);
    266     delete[] str;
    267     return OK;
    268 }
    269 
    270 status_t TimedTextSRTSource::extractAndAppendLocalDescriptions(
    271         int64_t timeUs, const AString &text, Parcel *parcel) {
    272     const void *data = text.c_str();
    273     size_t size = text.size();
    274     int32_t flag = TextDescriptions::LOCAL_DESCRIPTIONS |
    275                    TextDescriptions::OUT_OF_BAND_TEXT_SRT;
    276 
    277     if (size > 0) {
    278         return TextDescriptions::getParcelOfDescriptions(
    279                 (const uint8_t *)data, size, flag, timeUs / 1000, parcel);
    280     }
    281     return OK;
    282 }
    283 
    284 int TimedTextSRTSource::compareExtendedRangeAndTime(size_t index, int64_t timeUs) {
    285     CHECK_LT(index, mTextVector.size());
    286     int64_t endTimeUs = mTextVector.valueAt(index).endTimeUs;
    287     int64_t startTimeUs = (index > 0) ?
    288             mTextVector.valueAt(index - 1).endTimeUs : 0;
    289     if (timeUs >= startTimeUs && timeUs < endTimeUs) {
    290         return 0;
    291     } else if (endTimeUs <= timeUs) {
    292         return -1;
    293     } else {
    294         return 1;
    295     }
    296 }
    297 
    298 }  // namespace android
    299