1 /* 2 * Copyright (C) 2017 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 package com.google.android.exoplayer2.text.cea; 17 18 import android.util.Log; 19 import com.google.android.exoplayer2.C; 20 import com.google.android.exoplayer2.extractor.TrackOutput; 21 import com.google.android.exoplayer2.util.ParsableByteArray; 22 23 /** 24 * Utility methods for handling CEA-608/708 messages. 25 */ 26 public final class CeaUtil { 27 28 private static final String TAG = "CeaUtil"; 29 30 private static final int PAYLOAD_TYPE_CC = 4; 31 private static final int COUNTRY_CODE = 0xB5; 32 private static final int PROVIDER_CODE = 0x31; 33 private static final int USER_ID = 0x47413934; // "GA94" 34 private static final int USER_DATA_TYPE_CODE = 0x3; 35 36 /** 37 * Consumes the unescaped content of an SEI NAL unit, writing the content of any CEA-608 messages 38 * as samples to all of the provided outputs. 39 * 40 * @param presentationTimeUs The presentation time in microseconds for any samples. 41 * @param seiBuffer The unescaped SEI NAL unit data, excluding the NAL unit start code and type. 42 * @param outputs The outputs to which any samples should be written. 43 */ consume(long presentationTimeUs, ParsableByteArray seiBuffer, TrackOutput[] outputs)44 public static void consume(long presentationTimeUs, ParsableByteArray seiBuffer, 45 TrackOutput[] outputs) { 46 while (seiBuffer.bytesLeft() > 1 /* last byte will be rbsp_trailing_bits */) { 47 int payloadType = readNon255TerminatedValue(seiBuffer); 48 int payloadSize = readNon255TerminatedValue(seiBuffer); 49 // Process the payload. 50 if (payloadSize == -1 || payloadSize > seiBuffer.bytesLeft()) { 51 // This might occur if we're trying to read an encrypted SEI NAL unit. 52 Log.w(TAG, "Skipping remainder of malformed SEI NAL unit."); 53 seiBuffer.setPosition(seiBuffer.limit()); 54 } else if (isSeiMessageCea608(payloadType, payloadSize, seiBuffer)) { 55 // Ignore country_code (1) + provider_code (2) + user_identifier (4) 56 // + user_data_type_code (1). 57 seiBuffer.skipBytes(8); 58 // Ignore first three bits: reserved (1) + process_cc_data_flag (1) + zero_bit (1). 59 int ccCount = seiBuffer.readUnsignedByte() & 0x1F; 60 // Ignore em_data (1) 61 seiBuffer.skipBytes(1); 62 // Each data packet consists of 24 bits: marker bits (5) + cc_valid (1) + cc_type (2) 63 // + cc_data_1 (8) + cc_data_2 (8). 64 int sampleLength = ccCount * 3; 65 int sampleStartPosition = seiBuffer.getPosition(); 66 for (TrackOutput output : outputs) { 67 seiBuffer.setPosition(sampleStartPosition); 68 output.sampleData(seiBuffer, sampleLength); 69 output.sampleMetadata(presentationTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleLength, 0, null); 70 } 71 // Ignore trailing information in SEI, if any. 72 seiBuffer.skipBytes(payloadSize - (10 + ccCount * 3)); 73 } else { 74 seiBuffer.skipBytes(payloadSize); 75 } 76 } 77 } 78 79 /** 80 * Reads a value from the provided buffer consisting of zero or more 0xFF bytes followed by a 81 * terminating byte not equal to 0xFF. The returned value is ((0xFF * N) + T), where N is the 82 * number of 0xFF bytes and T is the value of the terminating byte. 83 * 84 * @param buffer The buffer from which to read the value. 85 * @returns The read value, or -1 if the end of the buffer is reached before a value is read. 86 */ readNon255TerminatedValue(ParsableByteArray buffer)87 private static int readNon255TerminatedValue(ParsableByteArray buffer) { 88 int b; 89 int value = 0; 90 do { 91 if (buffer.bytesLeft() == 0) { 92 return -1; 93 } 94 b = buffer.readUnsignedByte(); 95 value += b; 96 } while (b == 0xFF); 97 return value; 98 } 99 100 /** 101 * Inspects an sei message to determine whether it contains CEA-608. 102 * <p> 103 * The position of {@code payload} is left unchanged. 104 * 105 * @param payloadType The payload type of the message. 106 * @param payloadLength The length of the payload. 107 * @param payload A {@link ParsableByteArray} containing the payload. 108 * @return Whether the sei message contains CEA-608. 109 */ isSeiMessageCea608(int payloadType, int payloadLength, ParsableByteArray payload)110 private static boolean isSeiMessageCea608(int payloadType, int payloadLength, 111 ParsableByteArray payload) { 112 if (payloadType != PAYLOAD_TYPE_CC || payloadLength < 8) { 113 return false; 114 } 115 int startPosition = payload.getPosition(); 116 int countryCode = payload.readUnsignedByte(); 117 int providerCode = payload.readUnsignedShort(); 118 int userIdentifier = payload.readInt(); 119 int userDataTypeCode = payload.readUnsignedByte(); 120 payload.setPosition(startPosition); 121 return countryCode == COUNTRY_CODE && providerCode == PROVIDER_CODE 122 && userIdentifier == USER_ID && userDataTypeCode == USER_DATA_TYPE_CODE; 123 } 124 CeaUtil()125 private CeaUtil() {} 126 127 } 128