public final class

RtpAc3Reader

extends java.lang.Object

implements RtpPayloadReader

 java.lang.Object

↳androidx.media3.exoplayer.rtsp.reader.RtpAc3Reader

Gradle dependencies

compile group: 'androidx.media3', name: 'media3-exoplayer-rtsp', version: '1.0.0-alpha03'

  • groupId: androidx.media3
  • artifactId: media3-exoplayer-rtsp
  • version: 1.0.0-alpha03

Artifact androidx.media3:media3-exoplayer-rtsp:1.0.0-alpha03 it located at Google repository (https://maven.google.com/)

Overview

Parses an AC3 byte stream carried on RTP packets, and extracts AC3 frames.

Summary

Constructors
publicRtpAc3Reader(RtpPayloadFormat payloadFormat)

Methods
public voidconsume(ParsableByteArray data, long timestamp, int sequenceNumber, boolean rtpMarker)

public voidcreateTracks(ExtractorOutput extractorOutput, int trackId)

public voidonReceivingFirstPacket(long timestamp, int sequenceNumber)

public voidseek(long nextRtpTimestamp, long timeUs)

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public RtpAc3Reader(RtpPayloadFormat payloadFormat)

Methods

public void createTracks(ExtractorOutput extractorOutput, int trackId)

public void onReceivingFirstPacket(long timestamp, int sequenceNumber)

public void consume(ParsableByteArray data, long timestamp, int sequenceNumber, boolean rtpMarker)

public void seek(long nextRtpTimestamp, long timeUs)

Source

/*
 * Copyright 2021 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.exoplayer.rtsp.reader;

import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.common.util.Util.castNonNull;

import androidx.media3.common.C;
import androidx.media3.common.util.ParsableBitArray;
import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.rtsp.RtpPayloadFormat;
import androidx.media3.extractor.Ac3Util;
import androidx.media3.extractor.ExtractorOutput;
import androidx.media3.extractor.TrackOutput;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;

/** Parses an AC3 byte stream carried on RTP packets, and extracts AC3 frames. */
@UnstableApi
/* package */ public final class RtpAc3Reader implements RtpPayloadReader {

  /** AC3 frame types defined in RFC4184 Section 4.1.1. */
  private static final int AC3_FRAME_TYPE_COMPLETE_FRAME = 0;
  /** Initial fragment of frame which includes the first 5/8ths of the frame. */
  private static final int AC3_FRAME_TYPE_INITIAL_FRAGMENT_A = 1;
  /** Initial fragment of frame which does not include the first 5/8ths of the frame. */
  private static final int AC3_FRAME_TYPE_INITIAL_FRAGMENT_B = 2;

  private static final int AC3_FRAME_TYPE_NON_INITIAL_FRAGMENT = 3;

  /** AC3 payload header size in bytes. */
  private static final int AC3_PAYLOAD_HEADER_SIZE = 2;

  private final RtpPayloadFormat payloadFormat;
  private final ParsableBitArray scratchBitBuffer;

  private @MonotonicNonNull TrackOutput trackOutput;
  private int numBytesPendingMetadataOutput;
  private long firstReceivedTimestamp;
  private long sampleTimeUsOfFramePendingMetadataOutput;
  private long startTimeOffsetUs;

  public RtpAc3Reader(RtpPayloadFormat payloadFormat) {
    this.payloadFormat = payloadFormat;
    scratchBitBuffer = new ParsableBitArray();
    firstReceivedTimestamp = C.TIME_UNSET;
  }

  @Override
  public void createTracks(ExtractorOutput extractorOutput, int trackId) {
    trackOutput = extractorOutput.track(trackId, C.TRACK_TYPE_AUDIO);
    trackOutput.format(payloadFormat.format);
  }

  @Override
  public void onReceivingFirstPacket(long timestamp, int sequenceNumber) {
    checkState(firstReceivedTimestamp == C.TIME_UNSET);
    firstReceivedTimestamp = timestamp;
  }

  @Override
  public void consume(
      ParsableByteArray data, long timestamp, int sequenceNumber, boolean rtpMarker) {
    /*
    AC-3 payload as an RTP payload (RFC4184).
      +-+-+-+-+-+-+-+-+-+-+-+-+-+- .. +-+-+-+-+-+-+-+
      | Payload | Frame | Frame |     | Frame |
      | Header  |  (1)  |  (2)  |     |  (n)  |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+- .. +-+-+-+-+-+-+-+

    The payload header:
       0                   1
       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |    MBZ    | FT|       NF      |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      FT: frame type.
      NF: number of frames/fragments.
     */
    int frameType = data.readUnsignedByte() & 0x3;
    int numOfFrames = data.readUnsignedByte() & 0xFF;

    long sampleTimeUs =
        toSampleTimeUs(
            startTimeOffsetUs, timestamp, firstReceivedTimestamp, payloadFormat.clockRate);

    switch (frameType) {
      case AC3_FRAME_TYPE_COMPLETE_FRAME:
        maybeOutputSampleMetadata();
        if (numOfFrames == 1) {
          // Single AC3 frame in one RTP packet.
          processSingleFramePacket(data, sampleTimeUs);
        } else {
          // Multiple AC3 frames in one RTP packet.
          processMultiFramePacket(data, numOfFrames, sampleTimeUs);
        }
        break;

      case AC3_FRAME_TYPE_INITIAL_FRAGMENT_A:
      case AC3_FRAME_TYPE_INITIAL_FRAGMENT_B:
        maybeOutputSampleMetadata();
        // Falls through.
      case AC3_FRAME_TYPE_NON_INITIAL_FRAGMENT:
        // The content of an AC3 frame is split into multiple RTP packets.
        processFragmentedPacket(data, rtpMarker, frameType, sampleTimeUs);
        break;

      default:
        throw new IllegalArgumentException(String.valueOf(frameType));
    }
  }

  @Override
  public void seek(long nextRtpTimestamp, long timeUs) {
    firstReceivedTimestamp = nextRtpTimestamp;
    startTimeOffsetUs = timeUs;
  }

  private void processSingleFramePacket(ParsableByteArray data, long sampleTimeUs) {
    int frameSize = data.bytesLeft();
    checkNotNull(trackOutput).sampleData(data, frameSize);
    castNonNull(trackOutput)
        .sampleMetadata(
            /* timeUs= */ sampleTimeUs,
            /* flags= */ C.BUFFER_FLAG_KEY_FRAME,
            /* size= */ frameSize,
            /* offset= */ 0,
            /* cryptoData= */ null);
  }

  private void processMultiFramePacket(ParsableByteArray data, int numOfFrames, long sampleTimeUs) {
    // The size of each frame must be obtained by reading AC3 sync frame.
    scratchBitBuffer.reset(data.getData());
    // Move the read location after the AC3 payload header.
    scratchBitBuffer.skipBytes(AC3_PAYLOAD_HEADER_SIZE);

    for (int i = 0; i < numOfFrames; i++) {
      Ac3Util.SyncFrameInfo frameInfo = Ac3Util.parseAc3SyncframeInfo(scratchBitBuffer);

      checkNotNull(trackOutput).sampleData(data, frameInfo.frameSize);
      castNonNull(trackOutput)
          .sampleMetadata(
              /* timeUs= */ sampleTimeUs,
              /* flags= */ C.BUFFER_FLAG_KEY_FRAME,
              /* size= */ frameInfo.frameSize,
              /* offset= */ 0,
              /* cryptoData= */ null);

      sampleTimeUs += (frameInfo.sampleCount / frameInfo.sampleRate) * C.MICROS_PER_SECOND;
      // Advance the position by the number of bytes read.
      scratchBitBuffer.skipBytes(frameInfo.frameSize);
    }
  }

  private void processFragmentedPacket(
      ParsableByteArray data, boolean isFrameBoundary, int frameType, long sampleTimeUs) {
    int bytesToWrite = data.bytesLeft();
    checkNotNull(trackOutput).sampleData(data, bytesToWrite);
    numBytesPendingMetadataOutput += bytesToWrite;
    sampleTimeUsOfFramePendingMetadataOutput = sampleTimeUs;

    if (isFrameBoundary && frameType == AC3_FRAME_TYPE_NON_INITIAL_FRAGMENT) {
      // Last RTP packet in the series of fragmentation packets.
      outputSampleMetadataForFragmentedPackets();
    }
  }

  /**
   * Checks and outputs sample metadata, if the last packet of a series of fragmented packets is
   * lost.
   *
   * <p>Call this method only when receiving an initial packet, i.e. on packets with type
   *
   * <ul>
   *   <li>{@link #AC3_FRAME_TYPE_COMPLETE_FRAME},
   *   <li>{@link #AC3_FRAME_TYPE_INITIAL_FRAGMENT_A}, or
   *   <li>{@link #AC3_FRAME_TYPE_INITIAL_FRAGMENT_B}.
   * </ul>
   */
  private void maybeOutputSampleMetadata() {
    if (numBytesPendingMetadataOutput > 0) {
      outputSampleMetadataForFragmentedPackets();
    }
  }

  private void outputSampleMetadataForFragmentedPackets() {
    castNonNull(trackOutput)
        .sampleMetadata(
            /* timeUs= */ sampleTimeUsOfFramePendingMetadataOutput,
            /* flags= */ C.BUFFER_FLAG_KEY_FRAME,
            /* size= */ numBytesPendingMetadataOutput,
            /* offset= */ 0,
            /* cryptoData= */ null);
    numBytesPendingMetadataOutput = 0;
  }

  /** Returns the correct sample time from RTP timestamp, accounting for the AC3 sampling rate. */
  private static long toSampleTimeUs(
      long startTimeOffsetUs, long rtpTimestamp, long firstReceivedRtpTimestamp, int sampleRate) {
    return startTimeOffsetUs
        + Util.scaleLargeTimestamp(
            rtpTimestamp - firstReceivedRtpTimestamp,
            /* multiplier= */ C.MICROS_PER_SECOND,
            /* divisor= */ sampleRate);
  }
}