// @VisibleForTesting /* package */ VorbisSetup readSetupHeaders(ExtractorInput input, ParsableByteArray scratch) throws IOException, InterruptedException { if (vorbisIdHeader == null) { oggReader.readPacket(input, scratch); vorbisIdHeader = VorbisUtil.readVorbisIdentificationHeader(scratch); scratch.reset(); } if (commentHeader == null) { oggReader.readPacket(input, scratch); commentHeader = VorbisUtil.readVorbisCommentHeader(scratch); scratch.reset(); } oggReader.readPacket(input, scratch); // the third packet contains the setup header byte[] setupHeaderData = new byte[scratch.limit()]; // raw data of vorbis setup header has to be passed to decoder as CSD buffer #2 System.arraycopy(scratch.data, 0, setupHeaderData, 0, scratch.limit()); // partially decode setup header to get the modes Mode[] modes = VorbisUtil.readVorbisModes(scratch, vorbisIdHeader.channels); // we need the ilog of modes all the time when extracting, so we compute it once int iLogModes = VorbisUtil.iLog(modes.length - 1); scratch.reset(); return new VorbisSetup(vorbisIdHeader, commentHeader, setupHeaderData, modes, iLogModes); }
@Override public void seek() { oggReader.reset(); previousPacketBlockSize = -1; elapsedSamples = 0; seenFirstAudioPacket = false; scratch.reset(); }
@Override public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException, InterruptedException { if (vorbisSetup == null) { vorbisSetup = readSetupHeaders(input, scratch); ArrayList<byte[]> codecInitialisationData = new ArrayList<>(); codecInitialisationData.clear(); codecInitialisationData.add(vorbisSetup.idHeader.data); codecInitialisationData.add(vorbisSetup.setupHeaderData); long duration = input.getLength() == C.LENGTH_UNBOUNDED ? C.UNKNOWN_TIME_US : input.getLength() * 8000000 / vorbisSetup.idHeader.getApproximateBitrate(); trackOutput.format( MediaFormat.createAudioFormat( null, MimeTypes.AUDIO_VORBIS, this.vorbisSetup.idHeader.bitrateNominal, OGG_MAX_SEGMENT_SIZE * 255, duration, this.vorbisSetup.idHeader.channels, (int) this.vorbisSetup.idHeader.sampleRate, codecInitialisationData, null)); } if (oggReader.readPacket(input, scratch)) { // if this is an audio packet... if ((scratch.data[0] & 0x01) != 1) { // ... we need to decode the block size int packetBlockSize = decodeBlockSize(scratch.data[0], vorbisSetup); // a packet contains samples produced from overlapping the previous and current frame data // (https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-350001.3.2) int samplesInPacket = seenFirstAudioPacket ? (packetBlockSize + previousPacketBlockSize) / 4 : 0; // codec expects the number of samples appended to audio data appendNumberOfSamples(scratch, samplesInPacket); // calculate time and send audio data to codec long timeUs = elapsedSamples * C.MICROS_PER_SECOND / vorbisSetup.idHeader.sampleRate; trackOutput.sampleData(scratch, scratch.limit()); trackOutput.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, scratch.limit(), 0, null); // update state in members for next iteration seenFirstAudioPacket = true; elapsedSamples += samplesInPacket; previousPacketBlockSize = packetBlockSize; } scratch.reset(); return RESULT_CONTINUE; } return RESULT_END_OF_INPUT; }
@Override public boolean sniff(ExtractorInput input) throws IOException, InterruptedException { try { OggReader.PageHeader header = new OggReader.PageHeader(); OggReader.populatePageHeader(input, header, scratch, true); if ((header.type & 0x02) != 0x02) { throw new ParserException("expected page to be first page of a logical stream"); } input.resetPeekPosition(); } catch (ParserException e) { Log.e(TAG, e.getMessage()); return false; } return true; }