getAacStreamInfo_(tsParser, stream, duration, reference) {
    const ADTS = shaka.transmuxer.ADTS;
    const timescale = shaka.util.TsParser.Timescale;
    const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;

    /** @type {!Array<shaka.util.Mp4Generator.Mp4Sample>} */
    const samples = [];

    let info;

    let firstPts = null;

    /** @type {?number} */
    let nextStartOffset = null;
    /** @type {?Uint8Array} */
    let overflowBytes = null;

    for (const audioData of tsParser.getAudioData()) {
      let data = audioData.data;
      if (!data) {
        continue;
      }
      let offset = 0;
      if (nextStartOffset == -1 && overflowBytes) {
        data = Uint8ArrayUtils.concat(overflowBytes, audioData.data);
        nextStartOffset = null;
      } else if (nextStartOffset != null && overflowBytes) {
        offset = Math.max(0, nextStartOffset);
        const missingFrameData =
            Uint8ArrayUtils.concat(overflowBytes, data.subarray(0, offset));
        samples.push({
          data: missingFrameData,
          size: missingFrameData.byteLength,
          duration: ADTS.AAC_SAMPLES_PER_FRAME,
          cts: 0,
          flags: {
            isLeading: 0,
            isDependedOn: 0,
            hasRedundancy: 0,
            degradPrio: 0,
            dependsOn: 2,
            isNonSync: 0,
          },
        });
        overflowBytes = null;
        nextStartOffset = null;
      }
      info = ADTS.parseInfo(data, offset);
      if (!info) {
        throw new shaka.util.Error(
            shaka.util.Error.Severity.CRITICAL,
            shaka.util.Error.Category.MEDIA,
            shaka.util.Error.Code.TRANSMUXING_FAILED,
            reference ? reference.getUris()[0] : null);
      }
      stream.audioSamplingRate = info.sampleRate;
      stream.channelsCount = info.channelCount;
      if (firstPts == null && audioData.pts !== null) {
        firstPts = audioData.pts;
      }
      while (offset < data.length) {
        const header = ADTS.parseHeader(data, offset);
        if (!header) {
          overflowBytes = data.subarray(offset, data.length);
          nextStartOffset = -1;
          break;
        }
        const length = header.headerLength + header.frameLength;
        nextStartOffset = Math.max(0, offset + length - data.length);
        if (nextStartOffset != 0) {
          overflowBytes = data.subarray(
              offset + header.headerLength, offset + length);
        } else if (offset + length <= data.length) {
          const frameData = data.subarray(
              offset + header.headerLength, offset + length);

          samples.push({
            data: frameData,
            size: header.frameLength,
            duration: ADTS.AAC_SAMPLES_PER_FRAME,
            cts: 0,
            flags: {
              isLeading: 0,
              isDependedOn: 0,
              hasRedundancy: 0,
              degradPrio: 0,
              dependsOn: 2,
              isNonSync: 0,
            },
          });
        }
        offset += length;
      }
    }

    if (!info || firstPts == null) {
      if (!tsParser.getVideoData().length) {
        throw new shaka.util.Error(
            shaka.util.Error.Severity.CRITICAL,
            shaka.util.Error.Category.MEDIA,
            shaka.util.Error.Code.TRANSMUXING_FAILED,
            reference ? reference.getUris()[0] : null);
      }

      firstPts = reference.startTime * timescale;

      const allCodecs = shaka.util.MimeUtils.splitCodecs(stream.codecs);
      const audioCodec = shaka.util.ManifestParserUtils.guessCodecsSafe(
          shaka.util.ManifestParserUtils.ContentType.AUDIO, allCodecs);
      if (!audioCodec || !stream.channelsCount || !stream.audioSamplingRate) {
        throw new shaka.util.Error(
            shaka.util.Error.Severity.CRITICAL,
            shaka.util.Error.Category.MEDIA,
            shaka.util.Error.Code.TRANSMUXING_FAILED,
            reference ? reference.getUris()[0] : null);
      }
      info = {
        sampleRate: stream.audioSamplingRate,
        channelCount: stream.channelsCount,
        codec: audioCodec,
      };
      const silenceFrame =
          ADTS.getSilentFrame(audioCodec, stream.channelsCount);
      if (!silenceFrame) {
        throw new shaka.util.Error(
            shaka.util.Error.Severity.CRITICAL,
            shaka.util.Error.Category.MEDIA,
            shaka.util.Error.Code.TRANSMUXING_FAILED,
            reference ? reference.getUris()[0] : null);
      }
      const segmentDuration =
          (reference.endTime - reference.startTime) * timescale;
      const finalPTs = firstPts + segmentDuration;
      let currentPts = firstPts;
      while (currentPts < finalPTs) {
        samples.push({
          data: silenceFrame,
          size: silenceFrame.byteLength,
          duration: ADTS.AAC_SAMPLES_PER_FRAME,
          cts: 0,
          flags: {
            isLeading: 0,
            isDependedOn: 0,
            hasRedundancy: 0,
            degradPrio: 0,
            dependsOn: 2,
            isNonSync: 0,
          },
        });
        currentPts += ADTS.AAC_SAMPLES_PER_FRAME / info.sampleRate * timescale;
      }
    }

    /** @type {number} */
    const sampleRate = info.sampleRate;
    /** @type {number} */
    const baseMediaDecodeTime = firstPts / timescale * sampleRate;

    return {
      id: stream.id,
      type: shaka.util.ManifestParserUtils.ContentType.AUDIO,
      codecs: info.codec,
      encrypted: stream.encrypted && stream.drmInfos.length > 0,
      timescale: sampleRate,
      duration: duration,
      videoNalus: [],
      audioConfig: new Uint8Array([]),
      videoConfig: new Uint8Array([]),
      hSpacing: 0,
      vSpacing: 0,
      data: {
        sequenceNumber: this.frameIndex_,
        baseMediaDecodeTime: baseMediaDecodeTime,
        samples: samples,
      },
      stream: stream,
    };
  }
@param {shaka.util.TsParser} tsParser
@param {shaka.extern.Stream} stream
@param {number} duration
@param {?shaka.media.SegmentReference} reference
@return {shaka.util.Mp4Generator.StreamInfo}
@private