Search in sources :

Example 51 with ExtractorInput

use of com.google.android.exoplayer2.extractor.ExtractorInput in project ExoPlayer by google.

the class FragmentedMp4Extractor method readSample.

/**
 * Attempts to read the next sample in the current mdat atom. The read sample may be output or
 * skipped.
 *
 * <p>If there are no more samples in the current mdat atom then the parser state is transitioned
 * to {@link #STATE_READING_ATOM_HEADER} and {@code false} is returned.
 *
 * <p>It is possible for a sample to be partially read in the case that an exception is thrown. In
 * this case the method can be called again to read the remainder of the sample.
 *
 * @param input The {@link ExtractorInput} from which to read data.
 * @return Whether a sample was read. The read sample may have been output or skipped. False
 *     indicates that there are no samples left to read in the current mdat.
 * @throws IOException If an error occurs reading from the input.
 */
private boolean readSample(ExtractorInput input) throws IOException {
    @Nullable TrackBundle trackBundle = currentTrackBundle;
    if (trackBundle == null) {
        trackBundle = getNextTrackBundle(trackBundles);
        if (trackBundle == null) {
            // We've run out of samples in the current mdat. Discard any trailing data and prepare to
            // read the header of the next atom.
            int bytesToSkip = (int) (endOfMdatPosition - input.getPosition());
            if (bytesToSkip < 0) {
                throw ParserException.createForMalformedContainer("Offset to end of mdat was negative.", /* cause= */
                null);
            }
            input.skipFully(bytesToSkip);
            enterReadingAtomHeaderState();
            return false;
        }
        long nextDataPosition = trackBundle.getCurrentSampleOffset();
        // We skip bytes preceding the next sample to read.
        int bytesToSkip = (int) (nextDataPosition - input.getPosition());
        if (bytesToSkip < 0) {
            // Assume the sample data must be contiguous in the mdat with no preceding data.
            Log.w(TAG, "Ignoring negative offset to sample data.");
            bytesToSkip = 0;
        }
        input.skipFully(bytesToSkip);
        currentTrackBundle = trackBundle;
    }
    if (parserState == STATE_READING_SAMPLE_START) {
        sampleSize = trackBundle.getCurrentSampleSize();
        if (trackBundle.currentSampleIndex < trackBundle.firstSampleToOutputIndex) {
            input.skipFully(sampleSize);
            trackBundle.skipSampleEncryptionData();
            if (!trackBundle.next()) {
                currentTrackBundle = null;
            }
            parserState = STATE_READING_SAMPLE_START;
            return true;
        }
        if (trackBundle.moovSampleTable.track.sampleTransformation == Track.TRANSFORMATION_CEA608_CDAT) {
            sampleSize -= Atom.HEADER_SIZE;
            input.skipFully(Atom.HEADER_SIZE);
        }
        if (MimeTypes.AUDIO_AC4.equals(trackBundle.moovSampleTable.track.format.sampleMimeType)) {
            // AC4 samples need to be prefixed with a clear sample header.
            sampleBytesWritten = trackBundle.outputSampleEncryptionData(sampleSize, Ac4Util.SAMPLE_HEADER_SIZE);
            Ac4Util.getAc4SampleHeader(sampleSize, scratch);
            trackBundle.output.sampleData(scratch, Ac4Util.SAMPLE_HEADER_SIZE);
            sampleBytesWritten += Ac4Util.SAMPLE_HEADER_SIZE;
        } else {
            sampleBytesWritten = trackBundle.outputSampleEncryptionData(sampleSize, /* clearHeaderSize= */
            0);
        }
        sampleSize += sampleBytesWritten;
        parserState = STATE_READING_SAMPLE_CONTINUE;
        sampleCurrentNalBytesRemaining = 0;
    }
    Track track = trackBundle.moovSampleTable.track;
    TrackOutput output = trackBundle.output;
    long sampleTimeUs = trackBundle.getCurrentSamplePresentationTimeUs();
    if (timestampAdjuster != null) {
        sampleTimeUs = timestampAdjuster.adjustSampleTimestamp(sampleTimeUs);
    }
    if (track.nalUnitLengthFieldLength != 0) {
        // Zero the top three bytes of the array that we'll use to decode nal unit lengths, in case
        // they're only 1 or 2 bytes long.
        byte[] nalPrefixData = nalPrefix.getData();
        nalPrefixData[0] = 0;
        nalPrefixData[1] = 0;
        nalPrefixData[2] = 0;
        int nalUnitPrefixLength = track.nalUnitLengthFieldLength + 1;
        int nalUnitLengthFieldLengthDiff = 4 - track.nalUnitLengthFieldLength;
        // start codes as we encounter them.
        while (sampleBytesWritten < sampleSize) {
            if (sampleCurrentNalBytesRemaining == 0) {
                // Read the NAL length so that we know where we find the next one, and its type.
                input.readFully(nalPrefixData, nalUnitLengthFieldLengthDiff, nalUnitPrefixLength);
                nalPrefix.setPosition(0);
                int nalLengthInt = nalPrefix.readInt();
                if (nalLengthInt < 1) {
                    throw ParserException.createForMalformedContainer("Invalid NAL length", /* cause= */
                    null);
                }
                sampleCurrentNalBytesRemaining = nalLengthInt - 1;
                // Write a start code for the current NAL unit.
                nalStartCode.setPosition(0);
                output.sampleData(nalStartCode, 4);
                // Write the NAL unit type byte.
                output.sampleData(nalPrefix, 1);
                processSeiNalUnitPayload = ceaTrackOutputs.length > 0 && NalUnitUtil.isNalUnitSei(track.format.sampleMimeType, nalPrefixData[4]);
                sampleBytesWritten += 5;
                sampleSize += nalUnitLengthFieldLengthDiff;
            } else {
                int writtenBytes;
                if (processSeiNalUnitPayload) {
                    // Read and write the payload of the SEI NAL unit.
                    nalBuffer.reset(sampleCurrentNalBytesRemaining);
                    input.readFully(nalBuffer.getData(), 0, sampleCurrentNalBytesRemaining);
                    output.sampleData(nalBuffer, sampleCurrentNalBytesRemaining);
                    writtenBytes = sampleCurrentNalBytesRemaining;
                    // Unescape and process the SEI NAL unit.
                    int unescapedLength = NalUnitUtil.unescapeStream(nalBuffer.getData(), nalBuffer.limit());
                    // If the format is H.265/HEVC the NAL unit header has two bytes so skip one more byte.
                    nalBuffer.setPosition(MimeTypes.VIDEO_H265.equals(track.format.sampleMimeType) ? 1 : 0);
                    nalBuffer.setLimit(unescapedLength);
                    CeaUtil.consume(sampleTimeUs, nalBuffer, ceaTrackOutputs);
                } else {
                    // Write the payload of the NAL unit.
                    writtenBytes = output.sampleData(input, sampleCurrentNalBytesRemaining, false);
                }
                sampleBytesWritten += writtenBytes;
                sampleCurrentNalBytesRemaining -= writtenBytes;
            }
        }
    } else {
        while (sampleBytesWritten < sampleSize) {
            int writtenBytes = output.sampleData(input, sampleSize - sampleBytesWritten, false);
            sampleBytesWritten += writtenBytes;
        }
    }
    @C.BufferFlags int sampleFlags = trackBundle.getCurrentSampleFlags();
    // Encryption data.
    @Nullable TrackOutput.CryptoData cryptoData = null;
    @Nullable TrackEncryptionBox encryptionBox = trackBundle.getEncryptionBoxIfEncrypted();
    if (encryptionBox != null) {
        cryptoData = encryptionBox.cryptoData;
    }
    output.sampleMetadata(sampleTimeUs, sampleFlags, sampleSize, 0, cryptoData);
    // After we have the sampleTimeUs, we can commit all the pending metadata samples
    outputPendingMetadataSamples(sampleTimeUs);
    if (!trackBundle.next()) {
        currentTrackBundle = null;
    }
    parserState = STATE_READING_SAMPLE_START;
    return true;
}
Also used : Nullable(androidx.annotation.Nullable) TrackOutput(com.google.android.exoplayer2.extractor.TrackOutput)

Example 52 with ExtractorInput

use of com.google.android.exoplayer2.extractor.ExtractorInput in project ExoPlayer by google.

the class Mp3Extractor method synchronize.

private boolean synchronize(ExtractorInput input, boolean sniffing) throws IOException {
    int validFrameCount = 0;
    int candidateSynchronizedHeaderData = 0;
    int peekedId3Bytes = 0;
    int searchedBytes = 0;
    int searchLimitBytes = sniffing ? MAX_SNIFF_BYTES : MAX_SYNC_BYTES;
    input.resetPeekPosition();
    if (input.getPosition() == 0) {
        // We need to parse enough ID3 metadata to retrieve any gapless/seeking playback information
        // even if ID3 metadata parsing is disabled.
        boolean parseAllId3Frames = (flags & FLAG_DISABLE_ID3_METADATA) == 0;
        Id3Decoder.FramePredicate id3FramePredicate = parseAllId3Frames ? null : REQUIRED_ID3_FRAME_PREDICATE;
        metadata = id3Peeker.peekId3Data(input, id3FramePredicate);
        if (metadata != null) {
            gaplessInfoHolder.setFromMetadata(metadata);
        }
        peekedId3Bytes = (int) input.getPeekPosition();
        if (!sniffing) {
            input.skipFully(peekedId3Bytes);
        }
    }
    while (true) {
        if (peekEndOfStreamOrHeader(input)) {
            if (validFrameCount > 0) {
                // We reached the end of the stream but found at least one valid frame.
                break;
            }
            throw new EOFException();
        }
        scratch.setPosition(0);
        int headerData = scratch.readInt();
        int frameSize;
        if ((candidateSynchronizedHeaderData != 0 && !headersMatch(headerData, candidateSynchronizedHeaderData)) || (frameSize = MpegAudioUtil.getFrameSize(headerData)) == C.LENGTH_UNSET) {
            // The header doesn't match the candidate header or is invalid. Try the next byte offset.
            if (searchedBytes++ == searchLimitBytes) {
                if (!sniffing) {
                    throw ParserException.createForMalformedContainer("Searched too many bytes.", /* cause= */
                    null);
                }
                return false;
            }
            validFrameCount = 0;
            candidateSynchronizedHeaderData = 0;
            if (sniffing) {
                input.resetPeekPosition();
                input.advancePeekPosition(peekedId3Bytes + searchedBytes);
            } else {
                input.skipFully(1);
            }
        } else {
            // The header matches the candidate header and/or is valid.
            validFrameCount++;
            if (validFrameCount == 1) {
                synchronizedHeader.setForHeaderData(headerData);
                candidateSynchronizedHeaderData = headerData;
            } else if (validFrameCount == 4) {
                break;
            }
            input.advancePeekPosition(frameSize - 4);
        }
    }
    // Prepare to read the synchronized frame.
    if (sniffing) {
        input.skipFully(peekedId3Bytes + searchedBytes);
    } else {
        input.resetPeekPosition();
    }
    synchronizedHeaderData = candidateSynchronizedHeaderData;
    return true;
}
Also used : FramePredicate(com.google.android.exoplayer2.metadata.id3.Id3Decoder.FramePredicate) EOFException(java.io.EOFException) Id3Decoder(com.google.android.exoplayer2.metadata.id3.Id3Decoder)

Example 53 with ExtractorInput

use of com.google.android.exoplayer2.extractor.ExtractorInput in project ExoPlayer by google.

the class Mp3Extractor method computeSeeker.

private Seeker computeSeeker(ExtractorInput input) throws IOException {
    // Read past any seek frame and set the seeker based on metadata or a seek frame. Metadata
    // takes priority as it can provide greater precision.
    Seeker seekFrameSeeker = maybeReadSeekFrame(input);
    Seeker metadataSeeker = maybeHandleSeekMetadata(metadata, input.getPosition());
    if (disableSeeking) {
        return new UnseekableSeeker();
    }
    @Nullable Seeker resultSeeker = null;
    if ((flags & FLAG_ENABLE_INDEX_SEEKING) != 0) {
        long durationUs;
        long dataEndPosition = C.POSITION_UNSET;
        if (metadataSeeker != null) {
            durationUs = metadataSeeker.getDurationUs();
            dataEndPosition = metadataSeeker.getDataEndPosition();
        } else if (seekFrameSeeker != null) {
            durationUs = seekFrameSeeker.getDurationUs();
            dataEndPosition = seekFrameSeeker.getDataEndPosition();
        } else {
            durationUs = getId3TlenUs(metadata);
        }
        resultSeeker = new IndexSeeker(durationUs, /* dataStartPosition= */
        input.getPosition(), dataEndPosition);
    } else if (metadataSeeker != null) {
        resultSeeker = metadataSeeker;
    } else if (seekFrameSeeker != null) {
        resultSeeker = seekFrameSeeker;
    }
    if (resultSeeker == null || (!resultSeeker.isSeekable() && (flags & FLAG_ENABLE_CONSTANT_BITRATE_SEEKING) != 0)) {
        resultSeeker = getConstantBitrateSeeker(input, (flags & FLAG_ENABLE_CONSTANT_BITRATE_SEEKING_ALWAYS) != 0);
    }
    return resultSeeker;
}
Also used : UnseekableSeeker(com.google.android.exoplayer2.extractor.mp3.Seeker.UnseekableSeeker) UnseekableSeeker(com.google.android.exoplayer2.extractor.mp3.Seeker.UnseekableSeeker) Nullable(androidx.annotation.Nullable)

Example 54 with ExtractorInput

use of com.google.android.exoplayer2.extractor.ExtractorInput in project ExoPlayer by google.

the class Mp4Extractor method readAtomHeader.

private boolean readAtomHeader(ExtractorInput input) throws IOException {
    if (atomHeaderBytesRead == 0) {
        // Read the standard length atom header.
        if (!input.readFully(atomHeader.getData(), 0, Atom.HEADER_SIZE, true)) {
            processEndOfStreamReadingAtomHeader();
            return false;
        }
        atomHeaderBytesRead = Atom.HEADER_SIZE;
        atomHeader.setPosition(0);
        atomSize = atomHeader.readUnsignedInt();
        atomType = atomHeader.readInt();
    }
    if (atomSize == Atom.DEFINES_LARGE_SIZE) {
        // Read the large size.
        int headerBytesRemaining = Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE;
        input.readFully(atomHeader.getData(), Atom.HEADER_SIZE, headerBytesRemaining);
        atomHeaderBytesRead += headerBytesRemaining;
        atomSize = atomHeader.readUnsignedLongToLong();
    } else if (atomSize == Atom.EXTENDS_TO_END_SIZE) {
        // The atom extends to the end of the file. Note that if the atom is within a container we can
        // work out its size even if the input length is unknown.
        long endPosition = input.getLength();
        if (endPosition == C.LENGTH_UNSET) {
            @Nullable ContainerAtom containerAtom = containerAtoms.peek();
            if (containerAtom != null) {
                endPosition = containerAtom.endPosition;
            }
        }
        if (endPosition != C.LENGTH_UNSET) {
            atomSize = endPosition - input.getPosition() + atomHeaderBytesRead;
        }
    }
    if (atomSize < atomHeaderBytesRead) {
        throw ParserException.createForUnsupportedContainerFeature("Atom size less than header length (unsupported).");
    }
    if (shouldParseContainerAtom(atomType)) {
        long endPosition = input.getPosition() + atomSize - atomHeaderBytesRead;
        if (atomSize != atomHeaderBytesRead && atomType == Atom.TYPE_meta) {
            maybeSkipRemainingMetaAtomHeaderBytes(input);
        }
        containerAtoms.push(new ContainerAtom(atomType, endPosition));
        if (atomSize == atomHeaderBytesRead) {
            processAtomEnded(endPosition);
        } else {
            // Start reading the first child atom.
            enterReadingAtomHeaderState();
        }
    } else if (shouldParseLeafAtom(atomType)) {
        // We don't support parsing of leaf atoms that define extended atom sizes, or that have
        // lengths greater than Integer.MAX_VALUE.
        Assertions.checkState(atomHeaderBytesRead == Atom.HEADER_SIZE);
        Assertions.checkState(atomSize <= Integer.MAX_VALUE);
        ParsableByteArray atomData = new ParsableByteArray((int) atomSize);
        System.arraycopy(atomHeader.getData(), 0, atomData.getData(), 0, Atom.HEADER_SIZE);
        this.atomData = atomData;
        parserState = STATE_READING_ATOM_PAYLOAD;
    } else {
        processUnparsedAtom(input.getPosition() - atomHeaderBytesRead);
        atomData = null;
        parserState = STATE_READING_ATOM_PAYLOAD;
    }
    return true;
}
Also used : ParsableByteArray(com.google.android.exoplayer2.util.ParsableByteArray) ContainerAtom(com.google.android.exoplayer2.extractor.mp4.Atom.ContainerAtom) SeekPoint(com.google.android.exoplayer2.extractor.SeekPoint)

Example 55 with ExtractorInput

use of com.google.android.exoplayer2.extractor.ExtractorInput in project ExoPlayer by google.

the class SefReader method readSefData.

private void readSefData(ExtractorInput input, List<Metadata.Entry> slowMotionMetadataEntries) throws IOException {
    long dataStartOffset = input.getPosition();
    int totalDataLength = (int) (input.getLength() - input.getPosition() - tailLength);
    ParsableByteArray data = new ParsableByteArray(/* limit= */
    totalDataLength);
    input.readFully(data.getData(), 0, totalDataLength);
    for (int i = 0; i < dataReferences.size(); i++) {
        DataReference dataReference = dataReferences.get(i);
        int intendedPosition = (int) (dataReference.startOffset - dataStartOffset);
        data.setPosition(intendedPosition);
        // The data type is derived from the name because the SEF format has inconsistent data type
        // values.
        // data type (2), data sub info (2).
        data.skipBytes(4);
        int nameLength = data.readLittleEndianInt();
        String name = data.readString(nameLength);
        @DataType int dataType = nameToDataType(name);
        int remainingDataLength = dataReference.size - (8 + nameLength);
        switch(dataType) {
            case TYPE_SLOW_MOTION_DATA:
                slowMotionMetadataEntries.add(readSlowMotionData(data, remainingDataLength));
                break;
            case TYPE_SUPER_SLOW_MOTION_DATA:
            case TYPE_SUPER_SLOW_MOTION_BGM:
            case TYPE_SUPER_SLOW_MOTION_EDIT_DATA:
            case TYPE_SUPER_SLOW_DEFLICKERING_ON:
                break;
            default:
                throw new IllegalStateException();
        }
    }
}
Also used : ParsableByteArray(com.google.android.exoplayer2.util.ParsableByteArray)

Aggregations

FakeExtractorInput (com.google.android.exoplayer2.testutil.FakeExtractorInput)85 Test (org.junit.Test)71 ExtractorInput (com.google.android.exoplayer2.extractor.ExtractorInput)53 ParsableByteArray (com.google.android.exoplayer2.util.ParsableByteArray)39 FlacStreamMetadataHolder (com.google.android.exoplayer2.extractor.FlacMetadataReader.FlacStreamMetadataHolder)20 DataSpec (com.google.android.exoplayer2.upstream.DataSpec)17 DefaultExtractorInput (com.google.android.exoplayer2.extractor.DefaultExtractorInput)16 Nullable (androidx.annotation.Nullable)15 Metadata (com.google.android.exoplayer2.metadata.Metadata)13 SampleNumberHolder (com.google.android.exoplayer2.extractor.FlacFrameReader.SampleNumberHolder)9 FakeDataSource (com.google.android.exoplayer2.testutil.FakeDataSource)9 EOFException (java.io.EOFException)8 SeekPoint (com.google.android.exoplayer2.extractor.SeekPoint)7 RequiresNonNull (org.checkerframework.checker.nullness.qual.RequiresNonNull)7 ParserException (com.google.android.exoplayer2.ParserException)5 SeekMap (com.google.android.exoplayer2.extractor.SeekMap)5 TrackOutput (com.google.android.exoplayer2.extractor.TrackOutput)5 IOException (java.io.IOException)5 PositionHolder (com.google.android.exoplayer2.extractor.PositionHolder)3 Id3Decoder (com.google.android.exoplayer2.metadata.id3.Id3Decoder)3