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 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