AppInfoTableDecoder.java

/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package androidx.media3.extractor.metadata.dvbsi;

import androidx.annotation.Nullable;
import androidx.media3.common.Metadata;
import androidx.media3.common.util.ParsableBitArray;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.extractor.metadata.MetadataInputBuffer;
import androidx.media3.extractor.metadata.SimpleMetadataDecoder;
import com.google.common.base.Charsets;
import java.nio.ByteBuffer;
import java.util.ArrayList;

/**
 * Decoder for the DVB Application Information Table (AIT).
 *
 * <p>For more info on the AIT see section 5.3.4 of the <a
 * href="https://www.etsi.org/deliver/etsi_ts/102800_102899/102809/01.01.01_60/ts_102809v010101p.pdf">
 * DVB ETSI TS 102 809 v1.1.1 spec</a>.
 */
@UnstableApi
public final class AppInfoTableDecoder extends SimpleMetadataDecoder {

  /** See section 5.3.6. */
  private static final int DESCRIPTOR_TRANSPORT_PROTOCOL = 0x02;
  /** See section 5.3.7. */
  private static final int DESCRIPTOR_SIMPLE_APPLICATION_LOCATION = 0x15;

  /** See table 29 in section 5.3.6. */
  private static final int TRANSPORT_PROTOCOL_HTTP = 3;

  /** See table 16 in section 5.3.4.6. */
  public static final int APPLICATION_INFORMATION_TABLE_ID = 0x74;

  @Override
  @Nullable
  @SuppressWarnings("ByteBufferBackingArray") // Buffer validated by SimpleMetadataDecoder.decode
  protected Metadata decode(MetadataInputBuffer inputBuffer, ByteBuffer buffer) {
    int tableId = buffer.get();
    return tableId == APPLICATION_INFORMATION_TABLE_ID
        ? parseAit(new ParsableBitArray(buffer.array(), buffer.limit()))
        : null;
  }

  @Nullable
  private static Metadata parseAit(ParsableBitArray sectionData) {
    // tableId, section_syntax_indication, reserved_future_use, reserved
    sectionData.skipBits(12);
    int sectionLength = sectionData.readBits(12);
    int endOfSection = sectionData.getBytePosition() + sectionLength - 4 /* Ignore leading CRC */;

    // test_application_flag, application_type, reserved, version_number, current_next_indicator,
    // section_number, last_section_number, reserved_future_use
    sectionData.skipBits(44);

    int commonDescriptorsLength = sectionData.readBits(12);

    // Since we currently only keep URL and control code, which are unique per application,
    // there is no useful information in common descriptor.
    sectionData.skipBytes(commonDescriptorsLength);

    // reserved_future_use, application_loop_length
    sectionData.skipBits(16);

    ArrayList<AppInfoTable> appInfoTables = new ArrayList<>();
    while (sectionData.getBytePosition() < endOfSection) {
      @Nullable String urlBase = null;
      @Nullable String urlExtension = null;

      // application_identifier
      sectionData.skipBits(48);

      int controlCode = sectionData.readBits(8);

      // reserved_future_use
      sectionData.skipBits(4);

      int applicationDescriptorsLoopLength = sectionData.readBits(12);
      int positionOfNextApplication =
          sectionData.getBytePosition() + applicationDescriptorsLoopLength;
      while (sectionData.getBytePosition() < positionOfNextApplication) {
        int descriptorTag = sectionData.readBits(8);
        int descriptorLength = sectionData.readBits(8);
        int positionOfNextDescriptor = sectionData.getBytePosition() + descriptorLength;

        if (descriptorTag == DESCRIPTOR_TRANSPORT_PROTOCOL) {
          // See section 5.3.6.
          int protocolId = sectionData.readBits(16);
          // label
          sectionData.skipBits(8);

          if (protocolId == TRANSPORT_PROTOCOL_HTTP) {
            // See section 5.3.6.2.
            while (sectionData.getBytePosition() < positionOfNextDescriptor) {
              int urlBaseLength = sectionData.readBits(8);
              urlBase = sectionData.readBytesAsString(urlBaseLength, Charsets.US_ASCII);

              int extensionCount = sectionData.readBits(8);
              for (int urlExtensionIndex = 0;
                  urlExtensionIndex < extensionCount;
                  urlExtensionIndex++) {
                int urlExtensionLength = sectionData.readBits(8);
                sectionData.skipBytes(urlExtensionLength);
              }
            }
          }
        } else if (descriptorTag == DESCRIPTOR_SIMPLE_APPLICATION_LOCATION) {
          // See section 5.3.7.
          urlExtension = sectionData.readBytesAsString(descriptorLength, Charsets.US_ASCII);
        }

        sectionData.setPosition(positionOfNextDescriptor * 8);
      }

      sectionData.setPosition(positionOfNextApplication * 8);

      if (urlBase != null && urlExtension != null) {
        appInfoTables.add(new AppInfoTable(controlCode, urlBase + urlExtension));
      }
    }

    return appInfoTables.isEmpty() ? null : new Metadata(appInfoTables);
  }
}