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