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