1 /*
2 * Copyright 2014 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 "NuPlayerCCDecoder"
19 #include <utils/Log.h>
20 #include <inttypes.h>
21
22 #include "NuPlayerCCDecoder.h"
23
24 #include <media/stagefright/foundation/ABitReader.h>
25 #include <media/stagefright/foundation/ABuffer.h>
26 #include <media/stagefright/foundation/ADebug.h>
27 #include <media/stagefright/foundation/AMessage.h>
28 #include <media/stagefright/MediaDefs.h>
29
30 namespace android {
31
32 struct CCData {
CCDataandroid::CCData33 CCData(uint8_t type, uint8_t data1, uint8_t data2)
34 : mType(type), mData1(data1), mData2(data2) {
35 }
getChannelandroid::CCData36 bool getChannel(size_t *channel) const {
37 if (mData1 >= 0x10 && mData1 <= 0x1f) {
38 *channel = (mData1 >= 0x18 ? 1 : 0) + (mType ? 2 : 0);
39 return true;
40 }
41 return false;
42 }
43
44 uint8_t mType;
45 uint8_t mData1;
46 uint8_t mData2;
47 };
48
isNullPad(CCData * cc)49 static bool isNullPad(CCData *cc) {
50 return cc->mData1 < 0x10 && cc->mData2 < 0x10;
51 }
52
dumpBytePair(const sp<ABuffer> & ccBuf)53 static void dumpBytePair(const sp<ABuffer> &ccBuf) {
54 size_t offset = 0;
55 AString out;
56
57 while (offset < ccBuf->size()) {
58 char tmp[128];
59
60 CCData *cc = (CCData *) (ccBuf->data() + offset);
61
62 if (isNullPad(cc)) {
63 // 1 null pad or XDS metadata, ignore
64 offset += sizeof(CCData);
65 continue;
66 }
67
68 if (cc->mData1 >= 0x20 && cc->mData1 <= 0x7f) {
69 // 2 basic chars
70 sprintf(tmp, "[%d]Basic: %c %c", cc->mType, cc->mData1, cc->mData2);
71 } else if ((cc->mData1 == 0x11 || cc->mData1 == 0x19)
72 && cc->mData2 >= 0x30 && cc->mData2 <= 0x3f) {
73 // 1 special char
74 sprintf(tmp, "[%d]Special: %02x %02x", cc->mType, cc->mData1, cc->mData2);
75 } else if ((cc->mData1 == 0x12 || cc->mData1 == 0x1A)
76 && cc->mData2 >= 0x20 && cc->mData2 <= 0x3f){
77 // 1 Spanish/French char
78 sprintf(tmp, "[%d]Spanish: %02x %02x", cc->mType, cc->mData1, cc->mData2);
79 } else if ((cc->mData1 == 0x13 || cc->mData1 == 0x1B)
80 && cc->mData2 >= 0x20 && cc->mData2 <= 0x3f){
81 // 1 Portuguese/German/Danish char
82 sprintf(tmp, "[%d]German: %02x %02x", cc->mType, cc->mData1, cc->mData2);
83 } else if ((cc->mData1 == 0x11 || cc->mData1 == 0x19)
84 && cc->mData2 >= 0x20 && cc->mData2 <= 0x2f){
85 // Mid-Row Codes (Table 69)
86 sprintf(tmp, "[%d]Mid-row: %02x %02x", cc->mType, cc->mData1, cc->mData2);
87 } else if (((cc->mData1 == 0x14 || cc->mData1 == 0x1c)
88 && cc->mData2 >= 0x20 && cc->mData2 <= 0x2f)
89 ||
90 ((cc->mData1 == 0x17 || cc->mData1 == 0x1f)
91 && cc->mData2 >= 0x21 && cc->mData2 <= 0x23)){
92 // Misc Control Codes (Table 70)
93 sprintf(tmp, "[%d]Ctrl: %02x %02x", cc->mType, cc->mData1, cc->mData2);
94 } else if ((cc->mData1 & 0x70) == 0x10
95 && (cc->mData2 & 0x40) == 0x40
96 && ((cc->mData1 & 0x07) || !(cc->mData2 & 0x20)) ) {
97 // Preamble Address Codes (Table 71)
98 sprintf(tmp, "[%d]PAC: %02x %02x", cc->mType, cc->mData1, cc->mData2);
99 } else {
100 sprintf(tmp, "[%d]Invalid: %02x %02x", cc->mType, cc->mData1, cc->mData2);
101 }
102
103 if (out.size() > 0) {
104 out.append(", ");
105 }
106
107 out.append(tmp);
108
109 offset += sizeof(CCData);
110 }
111
112 ALOGI("%s", out.c_str());
113 }
114
CCDecoder(const sp<AMessage> & notify)115 NuPlayer::CCDecoder::CCDecoder(const sp<AMessage> ¬ify)
116 : mNotify(notify),
117 mCurrentChannel(0),
118 mSelectedTrack(-1) {
119 for (size_t i = 0; i < sizeof(mTrackIndices)/sizeof(mTrackIndices[0]); ++i) {
120 mTrackIndices[i] = -1;
121 }
122 }
123
getTrackCount() const124 size_t NuPlayer::CCDecoder::getTrackCount() const {
125 return mFoundChannels.size();
126 }
127
getTrackInfo(size_t index) const128 sp<AMessage> NuPlayer::CCDecoder::getTrackInfo(size_t index) const {
129 if (!isTrackValid(index)) {
130 return NULL;
131 }
132
133 sp<AMessage> format = new AMessage();
134
135 format->setInt32("type", MEDIA_TRACK_TYPE_SUBTITLE);
136 format->setString("language", "und");
137 format->setString("mime", MEDIA_MIMETYPE_TEXT_CEA_608);
138 //CC1, field 0 channel 0
139 bool isDefaultAuto = (mFoundChannels[index] == 0);
140 format->setInt32("auto", isDefaultAuto);
141 format->setInt32("default", isDefaultAuto);
142 format->setInt32("forced", 0);
143
144 return format;
145 }
146
selectTrack(size_t index,bool select)147 status_t NuPlayer::CCDecoder::selectTrack(size_t index, bool select) {
148 if (!isTrackValid(index)) {
149 return BAD_VALUE;
150 }
151
152 if (select) {
153 if (mSelectedTrack == (ssize_t)index) {
154 ALOGE("track %zu already selected", index);
155 return BAD_VALUE;
156 }
157 ALOGV("selected track %zu", index);
158 mSelectedTrack = index;
159 } else {
160 if (mSelectedTrack != (ssize_t)index) {
161 ALOGE("track %zu is not selected", index);
162 return BAD_VALUE;
163 }
164 ALOGV("unselected track %zu", index);
165 mSelectedTrack = -1;
166 }
167
168 return OK;
169 }
170
isSelected() const171 bool NuPlayer::CCDecoder::isSelected() const {
172 return mSelectedTrack >= 0 && mSelectedTrack < (int32_t) getTrackCount();
173 }
174
isTrackValid(size_t index) const175 bool NuPlayer::CCDecoder::isTrackValid(size_t index) const {
176 return index < getTrackCount();
177 }
178
getTrackIndex(size_t channel) const179 int32_t NuPlayer::CCDecoder::getTrackIndex(size_t channel) const {
180 if (channel < sizeof(mTrackIndices)/sizeof(mTrackIndices[0])) {
181 return mTrackIndices[channel];
182 }
183 return -1;
184 }
185
186 // returns true if a new CC track is found
extractFromSEI(const sp<ABuffer> & accessUnit)187 bool NuPlayer::CCDecoder::extractFromSEI(const sp<ABuffer> &accessUnit) {
188 int64_t timeUs;
189 CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
190
191 sp<ABuffer> sei;
192 if (!accessUnit->meta()->findBuffer("sei", &sei) || sei == NULL) {
193 return false;
194 }
195
196 bool trackAdded = false;
197
198 NALBitReader br(sei->data() + 1, sei->size() - 1);
199 // sei_message()
200 while (br.atLeastNumBitsLeft(16)) { // at least 16-bit for sei_message()
201 uint32_t payload_type = 0;
202 size_t payload_size = 0;
203 uint8_t last_byte;
204
205 do {
206 last_byte = br.getBits(8);
207 payload_type += last_byte;
208 } while (last_byte == 0xFF);
209
210 do {
211 last_byte = br.getBits(8);
212 payload_size += last_byte;
213 } while (last_byte == 0xFF);
214
215 // sei_payload()
216 if (payload_type == 4) {
217 // user_data_registered_itu_t_t35()
218
219 // ATSC A/72: 6.4.2
220 uint8_t itu_t_t35_country_code = br.getBits(8);
221 uint16_t itu_t_t35_provider_code = br.getBits(16);
222 uint32_t user_identifier = br.getBits(32);
223 uint8_t user_data_type_code = br.getBits(8);
224
225 payload_size -= 1 + 2 + 4 + 1;
226
227 if (itu_t_t35_country_code == 0xB5
228 && itu_t_t35_provider_code == 0x0031
229 && user_identifier == 'GA94'
230 && user_data_type_code == 0x3) {
231 // MPEG_cc_data()
232 // ATSC A/53 Part 4: 6.2.3.1
233 br.skipBits(1); //process_em_data_flag
234 bool process_cc_data_flag = br.getBits(1);
235 br.skipBits(1); //additional_data_flag
236 size_t cc_count = br.getBits(5);
237 br.skipBits(8); // em_data;
238 payload_size -= 2;
239
240 if (process_cc_data_flag) {
241 AString out;
242
243 sp<ABuffer> ccBuf = new ABuffer(cc_count * sizeof(CCData));
244 ccBuf->setRange(0, 0);
245
246 for (size_t i = 0; i < cc_count; i++) {
247 uint8_t marker = br.getBits(5);
248 CHECK_EQ(marker, 0x1f);
249
250 bool cc_valid = br.getBits(1);
251 uint8_t cc_type = br.getBits(2);
252 // remove odd parity bit
253 uint8_t cc_data_1 = br.getBits(8) & 0x7f;
254 uint8_t cc_data_2 = br.getBits(8) & 0x7f;
255
256 if (cc_valid
257 && (cc_type == 0 || cc_type == 1)) {
258 CCData cc(cc_type, cc_data_1, cc_data_2);
259 if (!isNullPad(&cc)) {
260 size_t channel;
261 if (cc.getChannel(&channel) && getTrackIndex(channel) < 0) {
262 mTrackIndices[channel] = mFoundChannels.size();
263 mFoundChannels.push_back(channel);
264 trackAdded = true;
265 }
266 memcpy(ccBuf->data() + ccBuf->size(),
267 (void *)&cc, sizeof(cc));
268 ccBuf->setRange(0, ccBuf->size() + sizeof(CCData));
269 }
270 }
271 }
272 payload_size -= cc_count * 3;
273
274 mCCMap.add(timeUs, ccBuf);
275 break;
276 }
277 } else {
278 ALOGV("Malformed SEI payload type 4");
279 }
280 } else {
281 ALOGV("Unsupported SEI payload type %d", payload_type);
282 }
283
284 // skipping remaining bits of this payload
285 br.skipBits(payload_size * 8);
286 }
287
288 return trackAdded;
289 }
290
filterCCBuf(const sp<ABuffer> & ccBuf,size_t index)291 sp<ABuffer> NuPlayer::CCDecoder::filterCCBuf(
292 const sp<ABuffer> &ccBuf, size_t index) {
293 sp<ABuffer> filteredCCBuf = new ABuffer(ccBuf->size());
294 filteredCCBuf->setRange(0, 0);
295
296 size_t cc_count = ccBuf->size() / sizeof(CCData);
297 const CCData* cc_data = (const CCData*)ccBuf->data();
298 for (size_t i = 0; i < cc_count; ++i) {
299 size_t channel;
300 if (cc_data[i].getChannel(&channel)) {
301 mCurrentChannel = channel;
302 }
303 if (mCurrentChannel == mFoundChannels[index]) {
304 memcpy(filteredCCBuf->data() + filteredCCBuf->size(),
305 (void *)&cc_data[i], sizeof(CCData));
306 filteredCCBuf->setRange(0, filteredCCBuf->size() + sizeof(CCData));
307 }
308 }
309
310 return filteredCCBuf;
311 }
312
decode(const sp<ABuffer> & accessUnit)313 void NuPlayer::CCDecoder::decode(const sp<ABuffer> &accessUnit) {
314 if (extractFromSEI(accessUnit)) {
315 ALOGI("Found CEA-608 track");
316 sp<AMessage> msg = mNotify->dup();
317 msg->setInt32("what", kWhatTrackAdded);
318 msg->post();
319 }
320 // TODO: extract CC from other sources
321 }
322
display(int64_t timeUs)323 void NuPlayer::CCDecoder::display(int64_t timeUs) {
324 if (!isTrackValid(mSelectedTrack)) {
325 ALOGE("Could not find current track(index=%d)", mSelectedTrack);
326 return;
327 }
328
329 ssize_t index = mCCMap.indexOfKey(timeUs);
330 if (index < 0) {
331 ALOGV("cc for timestamp %" PRId64 " not found", timeUs);
332 return;
333 }
334
335 sp<ABuffer> ccBuf = filterCCBuf(mCCMap.valueAt(index), mSelectedTrack);
336
337 if (ccBuf->size() > 0) {
338 #if 0
339 dumpBytePair(ccBuf);
340 #endif
341
342 ccBuf->meta()->setInt32("trackIndex", mSelectedTrack);
343 ccBuf->meta()->setInt64("timeUs", timeUs);
344 ccBuf->meta()->setInt64("durationUs", 0ll);
345
346 sp<AMessage> msg = mNotify->dup();
347 msg->setInt32("what", kWhatClosedCaptionData);
348 msg->setBuffer("buffer", ccBuf);
349 msg->post();
350 }
351
352 // remove all entries before timeUs
353 mCCMap.removeItemsAt(0, index + 1);
354 }
355
flush()356 void NuPlayer::CCDecoder::flush() {
357 mCCMap.clear();
358 }
359
360 } // namespace android
361
362