NuPlayerCCDecoder.cpp revision 7137ec7e005a5a6e3c0edb91cfacf16a31f4bf6a
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 30namespace android { 31 32struct CCData { 33 CCData(uint8_t type, uint8_t data1, uint8_t data2) 34 : mType(type), mData1(data1), mData2(data2) { 35 } 36 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 49static bool isNullPad(CCData *cc) { 50 return cc->mData1 < 0x10 && cc->mData2 < 0x10; 51} 52 53static 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 115NuPlayer::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 124size_t NuPlayer::CCDecoder::getTrackCount() const { 125 return mFoundChannels.size(); 126} 127 128sp<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 147status_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 171bool NuPlayer::CCDecoder::isSelected() const { 172 return mSelectedTrack >= 0 && mSelectedTrack < (int32_t) getTrackCount(); 173} 174 175bool NuPlayer::CCDecoder::isTrackValid(size_t index) const { 176 return index < getTrackCount(); 177} 178 179int32_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 187bool 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 291sp<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 313void 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 323void 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 356void NuPlayer::CCDecoder::flush() { 357 mCCMap.clear(); 358} 359 360} // namespace android 361 362