Search in sources :

Example 16 with Metadata

use of androidx.media3.common.Metadata in project media by androidx.

the class FlacMetadataReader method readId3Metadata.

/**
 * Reads ID3 Data.
 *
 * <p>If no exception is thrown, the peek position of {@code input} is aligned with the read
 * position.
 *
 * @param input Input stream to read the ID3 data from.
 * @param parseData Whether to parse the ID3 frames.
 * @return The parsed ID3 data, or {@code null} if there is no such data or if {@code parseData}
 *     is {@code false}.
 * @throws IOException If reading from the input fails. In this case, the read position is left
 *     unchanged and there is no guarantee on the peek position.
 */
@Nullable
public static Metadata readId3Metadata(ExtractorInput input, boolean parseData) throws IOException {
    input.resetPeekPosition();
    long startingPeekPosition = input.getPeekPosition();
    @Nullable Metadata id3Metadata = peekId3Metadata(input, parseData);
    int peekedId3Bytes = (int) (input.getPeekPosition() - startingPeekPosition);
    input.skipFully(peekedId3Bytes);
    return id3Metadata;
}
Also used : Metadata(androidx.media3.common.Metadata) Nullable(androidx.annotation.Nullable) Nullable(androidx.annotation.Nullable)

Example 17 with Metadata

use of androidx.media3.common.Metadata in project media by androidx.

the class AppInfoTableDecoder method parseAit.

@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;
    // 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);
}
Also used : ArrayList(java.util.ArrayList) Metadata(androidx.media3.common.Metadata) Nullable(androidx.annotation.Nullable) Nullable(androidx.annotation.Nullable)

Example 18 with Metadata

use of androidx.media3.common.Metadata in project media by androidx.

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(androidx.media3.extractor.TrackOutput)

Example 19 with Metadata

use of androidx.media3.common.Metadata in project media by androidx.

the class FragmentedMp4Extractor method onEmsgLeafAtomRead.

/**
 * Handles an emsg atom (defined in 23009-1).
 */
private void onEmsgLeafAtomRead(ParsableByteArray atom) {
    if (emsgTrackOutputs.length == 0) {
        return;
    }
    atom.setPosition(Atom.HEADER_SIZE);
    int fullAtom = atom.readInt();
    int version = Atom.parseFullAtomVersion(fullAtom);
    String schemeIdUri;
    String value;
    long timescale;
    // Only set if version == 0
    long presentationTimeDeltaUs = C.TIME_UNSET;
    long sampleTimeUs = C.TIME_UNSET;
    long durationMs;
    long id;
    switch(version) {
        case 0:
            schemeIdUri = checkNotNull(atom.readNullTerminatedString());
            value = checkNotNull(atom.readNullTerminatedString());
            timescale = atom.readUnsignedInt();
            presentationTimeDeltaUs = Util.scaleLargeTimestamp(atom.readUnsignedInt(), C.MICROS_PER_SECOND, timescale);
            if (segmentIndexEarliestPresentationTimeUs != C.TIME_UNSET) {
                sampleTimeUs = segmentIndexEarliestPresentationTimeUs + presentationTimeDeltaUs;
            }
            durationMs = Util.scaleLargeTimestamp(atom.readUnsignedInt(), C.MILLIS_PER_SECOND, timescale);
            id = atom.readUnsignedInt();
            break;
        case 1:
            timescale = atom.readUnsignedInt();
            sampleTimeUs = Util.scaleLargeTimestamp(atom.readUnsignedLongToLong(), C.MICROS_PER_SECOND, timescale);
            durationMs = Util.scaleLargeTimestamp(atom.readUnsignedInt(), C.MILLIS_PER_SECOND, timescale);
            id = atom.readUnsignedInt();
            schemeIdUri = checkNotNull(atom.readNullTerminatedString());
            value = checkNotNull(atom.readNullTerminatedString());
            break;
        default:
            Log.w(TAG, "Skipping unsupported emsg version: " + version);
            return;
    }
    byte[] messageData = new byte[atom.bytesLeft()];
    atom.readBytes(messageData, /*offset=*/
    0, atom.bytesLeft());
    EventMessage eventMessage = new EventMessage(schemeIdUri, value, durationMs, id, messageData);
    ParsableByteArray encodedEventMessage = new ParsableByteArray(eventMessageEncoder.encode(eventMessage));
    int sampleSize = encodedEventMessage.bytesLeft();
    // Output the sample data.
    for (TrackOutput emsgTrackOutput : emsgTrackOutputs) {
        encodedEventMessage.setPosition(0);
        emsgTrackOutput.sampleData(encodedEventMessage, sampleSize);
    }
    // Output the sample metadata.
    if (sampleTimeUs == C.TIME_UNSET) {
        // We're processing a v0 emsg atom, which contains a presentation time delta, and cannot yet
        // calculate its absolute sample timestamp. Defer outputting the metadata until we can.
        pendingMetadataSampleInfos.addLast(new MetadataSampleInfo(presentationTimeDeltaUs, /* sampleTimeIsRelative= */
        true, sampleSize));
        pendingMetadataSampleBytes += sampleSize;
    } else if (!pendingMetadataSampleInfos.isEmpty()) {
        // We also need to defer outputting metadata if pendingMetadataSampleInfos is non-empty, else
        // we will output metadata for samples in the wrong order. See:
        // https://github.com/google/ExoPlayer/issues/9996.
        pendingMetadataSampleInfos.addLast(new MetadataSampleInfo(sampleTimeUs, /* sampleTimeIsRelative= */
        false, sampleSize));
        pendingMetadataSampleBytes += sampleSize;
    } else {
        // We can output the sample metadata immediately.
        if (timestampAdjuster != null) {
            sampleTimeUs = timestampAdjuster.adjustSampleTimestamp(sampleTimeUs);
        }
        for (TrackOutput emsgTrackOutput : emsgTrackOutputs) {
            emsgTrackOutput.sampleMetadata(sampleTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleSize, /* offset= */
            0, null);
        }
    }
}
Also used : ParsableByteArray(androidx.media3.common.util.ParsableByteArray) EventMessage(androidx.media3.extractor.metadata.emsg.EventMessage) TrackOutput(androidx.media3.extractor.TrackOutput)

Example 20 with Metadata

use of androidx.media3.common.Metadata in project media by androidx.

the class FragmentedMp4Extractor method outputPendingMetadataSamples.

/**
 * Called immediately after outputting a non-metadata sample, to output any pending metadata
 * samples.
 *
 * @param sampleTimeUs The timestamp of the non-metadata sample that was just output.
 */
private void outputPendingMetadataSamples(long sampleTimeUs) {
    while (!pendingMetadataSampleInfos.isEmpty()) {
        MetadataSampleInfo metadataSampleInfo = pendingMetadataSampleInfos.removeFirst();
        pendingMetadataSampleBytes -= metadataSampleInfo.size;
        long metadataSampleTimeUs = metadataSampleInfo.sampleTimeUs;
        if (metadataSampleInfo.sampleTimeIsRelative) {
            // The metadata sample timestamp is relative to the timestamp of the non-metadata sample
            // that was just output. Make it absolute.
            metadataSampleTimeUs += sampleTimeUs;
        }
        if (timestampAdjuster != null) {
            metadataSampleTimeUs = timestampAdjuster.adjustSampleTimestamp(metadataSampleTimeUs);
        }
        for (TrackOutput emsgTrackOutput : emsgTrackOutputs) {
            emsgTrackOutput.sampleMetadata(metadataSampleTimeUs, C.BUFFER_FLAG_KEY_FRAME, metadataSampleInfo.size, pendingMetadataSampleBytes, null);
        }
    }
}
Also used : TrackOutput(androidx.media3.extractor.TrackOutput)

Aggregations

Metadata (androidx.media3.common.Metadata)81 Test (org.junit.Test)79 Nullable (androidx.annotation.Nullable)46 Format (androidx.media3.common.Format)18 MediaMetadata (androidx.media3.common.MediaMetadata)17 ArrayList (java.util.ArrayList)17 MediaItem (androidx.media3.common.MediaItem)16 FakeExtractorInput (androidx.media3.test.utils.FakeExtractorInput)14 ParsableByteArray (androidx.media3.common.util.ParsableByteArray)13 MediaMetadataCompat (android.support.v4.media.MediaMetadataCompat)10 CountDownLatch (java.util.concurrent.CountDownLatch)9 MotionPhotoMetadata (androidx.media3.extractor.metadata.mp4.MotionPhotoMetadata)8 Player (androidx.media3.common.Player)7 MediumTest (androidx.test.filters.MediumTest)7 TrackGroupArray (androidx.media3.common.TrackGroupArray)6 AtomicReference (java.util.concurrent.atomic.AtomicReference)6 MediaDescriptionCompat (android.support.v4.media.MediaDescriptionCompat)5 Timeline (androidx.media3.common.Timeline)5 DatabaseIOException (androidx.media3.database.DatabaseIOException)5 TrackOutput (androidx.media3.extractor.TrackOutput)5