public Ac3PassthroughTrackRenderer(SampleSource source, Handler eventHandler, EventListener listener) { mSource = source.register(); mEventHandler = eventHandler; mEventListener = listener; mTrackIndex = -1; mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DIRECT); mSampleHolder.ensureSpaceForWrite(DEFAULT_INPUT_BUFFER_SIZE); mOutputBuffer = ByteBuffer.allocate(DEFAULT_OUTPUT_BUFFER_SIZE); mFormatHolder = new MediaFormatHolder(); AUDIO_TRACK.restart(); mCodecCounters = new CodecCounters(); mMonitor = new AudioTrackMonitor(); mAudioClock = new AudioClock(); mTracksIndex = new ArrayList<>(); }
@Override public int readData(int track, long positionUs, MediaFormatHolder formatHolder, SampleHolder sampleHolder) { Assertions.checkState(mPrepared); Assertions.checkState(mTrackStates.get(track) != TRACK_STATE_DISABLED); if (mPendingDiscontinuities.get(track)) { return NOTHING_READ; } if (mTrackStates.get(track) != TRACK_STATE_FORMAT_SENT) { mSampleExtractor.getTrackMediaFormat(track, formatHolder); mTrackStates.set(track, TRACK_STATE_FORMAT_SENT); return FORMAT_READ; } mPendingSeekPositionUs = C.UNKNOWN_TIME_US; return mSampleExtractor.readSample(track, sampleHolder); }
protected int parseClosedCaption(ByteBuffer buffer, int offset, long presentationTimeUs) { // For the details of user_data_type_structure, see ATSC A/53 Part 4 - Table 6.9. int pos = offset; if (pos + 2 >= buffer.position()) { return offset; } boolean processCcDataFlag = (buffer.get(pos) & 64) != 0; int ccCount = buffer.get(pos) & 0x1f; pos += 2; if (!processCcDataFlag || pos + 3 * ccCount >= buffer.position() || ccCount == 0) { return offset; } SampleHolder holder = mCcSamplePool.acquireSample(CC_BUFFER_SIZE_IN_BYTES); for (int i = 0; i < 3 * ccCount; i++) { holder.data.put(buffer.get(pos++)); } holder.timeUs = presentationTimeUs; mPendingCcSamples.add(holder); return pos; }
@Override public void writeSample(int index, SampleHolder sample, ConditionVariable conditionVariable) throws IOException { sample.data.position(0).limit(sample.size); SampleHolder sampleToQueue = mSamplePool.acquireSample(sample.size); sampleToQueue.size = sample.size; sampleToQueue.clearData(); sampleToQueue.data.put(sample.data); sampleToQueue.timeUs = sample.timeUs; sampleToQueue.flags = sample.flags; synchronized (this) { if (mPlayingSampleQueues[index] != null) { mPlayingSampleQueues[index].queueSample(sampleToQueue); } } }
private boolean maybeReadSample(SampleQueue queue, int index) { if (queue.getLastQueuedPositionUs() != null && queue.getLastQueuedPositionUs() > mCurrentPlaybackPositionUs + BUFFER_NEEDED_US && queue.isDurationGreaterThan(CHUNK_DURATION_US)) { // The speed of queuing samples can be higher than the playback speed. // If the duration of the samples in the queue is not limited, // samples can be accumulated and there can be out-of-memory issues. // But, the throttling should provide enough samples for the player to // finish the buffering state. return false; } SampleHolder sample = mSampleChunkIoHelper.readSample(index); if (sample != null) { queue.queueSample(sample); return true; } return false; }
/** * Reads a sample if it is available. * * @return Returns a sample if it is available, null otherwise. * @throws IOException */ SampleHolder read() throws IOException { if (mChunk != null && mChunk.isReadFinished(this)) { SampleChunk next = mChunk.mNextChunk; mChunk.closeRead(); if (next != null) { next.openRead(); } reset(next); } if (mChunk != null) { try { return mChunk.read(this); } catch (IllegalStateException e) { // Write is finished and there is no additional buffer to read. Log.w(TAG, "Tried to read sample over EOS."); return null; } } else { return null; } }
@VisibleForTesting protected void write(SampleHolder sample, IoState state) throws IOException { if (mAccessFile == null || mNextChunk != null || !state.equals(this, mWriteOffset)) { throw new IllegalStateException("Requested write for wrong SampleChunk"); } mAccessFile.seek(mWriteOffset); mAccessFile.writeInt(sample.size); mAccessFile.writeInt(sample.flags); mAccessFile.writeLong(sample.timeUs); sample.data.position(0).limit(sample.size); mAccessFile.getChannel().position(mWriteOffset + SAMPLE_HEADER_LENGTH).write(sample.data); mWriteOffset += sample.size + SAMPLE_HEADER_LENGTH; state.mCurrentOffset = mWriteOffset; }
private void doOpenRead(IoParams params) throws IOException { int index = params.index; mIoHandler.removeMessages(MSG_READ, index); SampleChunk chunk = mBufferManager.getReadFile(mIds.get(index), params.positionUs); if (chunk == null) { String errorMessage = "Chunk ID:" + mIds.get(index) + " pos:" + params.positionUs + "is not found"; SoftPreconditions.checkNotNull(chunk, TAG, errorMessage); throw new IOException(errorMessage); } mReadIoStates[index].openRead(chunk); if (mHandlerReadSampleBuffers[index] != null) { SampleHolder sample; while ((sample = mHandlerReadSampleBuffers[index].poll()) != null) { mSamplePool.releaseSample(sample); } } mHandlerReadSampleBuffers[index] = params.readSampleBuffer; mIoHandler.sendMessage(mIoHandler.obtainMessage(MSG_READ, index)); }
/** * Reads the current sample, advancing the read index to the next sample. * * @param sampleHolder The holder into which the current sample should be written. * @return True if a sample was read. False if there is no current sample. */ public boolean readSample(SampleHolder sampleHolder,String mimeType) { // Write the sample information into the holder and extrasHolder. boolean haveSample = infoQueue.peekSample(sampleHolder, extrasHolder); if (!haveSample) { return false; } // Read encryption data if the sample is encrypted. if (sampleHolder.isEncrypted()) { readEncryptionData(sampleHolder, extrasHolder); } // Write the sample data into the holder. if (sampleHolder.data == null || sampleHolder.data.capacity() < sampleHolder.size) { sampleHolder.replaceBuffer(sampleHolder.size); } if (sampleHolder.data != null) { readData(extrasHolder.offset, sampleHolder.data, sampleHolder.size, mimeType); } // Advance the read head. long nextOffset = infoQueue.moveToNextSample(); dropDownstreamTo(nextOffset); return true; }
@Override public boolean handleMessage(Message msg) { Subtitle result; IOException error; SampleHolder holder = (SampleHolder) msg.obj; try { InputStream inputStream = new ByteArrayInputStream(holder.data.array(), 0, holder.size); result = parser.parse(inputStream, null, sampleHolder.timeUs); error = null; } catch (IOException e) { result = null; error = e; } synchronized (this) { if (sampleHolder != holder) { // A flush has occurred since this holder was posted. Do nothing. } else { this.result = result; this.error = error; this.parsing = false; } } return true; }
@Override public FlacDecoderException decode(InputBuffer inputBuffer, FlacOutputBuffer outputBuffer, boolean reset) { if (reset) { decoder.flush(); } SampleHolder sampleHolder = inputBuffer.sampleHolder; outputBuffer.timestampUs = sampleHolder.timeUs; sampleHolder.data.limit(sampleHolder.data.position()); sampleHolder.data.position(sampleHolder.data.position() - sampleHolder.size); outputBuffer.init(maxOutputBufferSize); decoder.setData(sampleHolder.data); int result = decoder.decodeSample(outputBuffer.data); if (result < 0) { return new FlacDecoderException("Frame decoding failed"); } outputBuffer.data.position(0); outputBuffer.data.limit(result); return null; }
/** * Reads the current sample, advancing the read index to the next sample. * * @param sampleHolder The holder into which the current sample should be written. * @return True if a sample was read. False if there is no current sample. */ public boolean readSample(SampleHolder sampleHolder) { // Write the sample information into the holder and extrasHolder. boolean haveSample = infoQueue.peekSample(sampleHolder, extrasHolder); if (!haveSample) { return false; } // Read encryption data if the sample is encrypted. if (sampleHolder.isEncrypted()) { readEncryptionData(sampleHolder, extrasHolder); } // Write the sample data into the holder. sampleHolder.ensureSpaceForWrite(sampleHolder.size); readData(extrasHolder.offset, sampleHolder.data, sampleHolder.size); // Advance the read head. long nextOffset = infoQueue.moveToNextSample(); dropDownstreamTo(nextOffset); return true; }
@Override public int read(NonBlockingInputStream inputStream, SampleHolder out) throws ParserException { try { int results = 0; while ((results & READ_TERMINATING_RESULTS) == 0) { switch (parserState) { case STATE_READING_ATOM_HEADER: results |= readAtomHeader(inputStream); break; case STATE_READING_ATOM_PAYLOAD: results |= readAtomPayload(inputStream); break; case STATE_READING_ENCRYPTION_DATA: results |= readEncryptionData(inputStream); break; default: results |= readOrSkipSample(inputStream, out); break; } } return results; } catch (Exception e) { throw new ParserException(e); } }
@Override public int read( NonBlockingInputStream inputStream, SampleHolder sampleHolder) throws ParserException { this.sampleHolder = sampleHolder; this.readResults = 0; while ((readResults & READ_TERMINATING_RESULTS) == 0) { int ebmlReadResult = reader.read(inputStream); if (ebmlReadResult == EbmlReader.READ_RESULT_NEED_MORE_DATA) { readResults |= WebmExtractor.RESULT_NEED_MORE_DATA; } else if (ebmlReadResult == EbmlReader.READ_RESULT_END_OF_STREAM) { readResults |= WebmExtractor.RESULT_END_OF_STREAM; } } this.sampleHolder = null; return readResults; }
@Override public boolean handleMessage(Message msg) { Subtitle result; IOException error; SampleHolder holder = (SampleHolder) msg.obj; try { InputStream inputStream = new ByteArrayInputStream(holder.data.array(), 0, holder.size); result = parser.parse(inputStream, null, sampleHolder.timeUs); error = null; } catch (IOException e) { result = null; error = e; } synchronized (this) { if (sampleHolder != holder) { // A flush has occurred since this holder was posted. Do nothing. } else { holder.data.position(0); this.result = result; this.error = error; this.parsing = false; } } return true; }
@Override public int readData(int track, long positionUs, MediaFormatHolder formatHolder, SampleHolder sampleHolder, boolean onlyReadDiscontinuity) throws IOException { Assertions.checkState(prepared); Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED); if (pendingDiscontinuities[track]) { pendingDiscontinuities[track] = false; return DISCONTINUITY_READ; } if (onlyReadDiscontinuity) { return NOTHING_READ; } if (trackStates[track] != TRACK_STATE_FORMAT_SENT) { sampleExtractor.getTrackMediaFormat(track, formatHolder); trackStates[track] = TRACK_STATE_FORMAT_SENT; return FORMAT_READ; } seekPositionUs = C.UNKNOWN_TIME_US; return sampleExtractor.readSample(track, sampleHolder); }
/** * Reads the current sample, advancing the read index to the next sample. * * @param sampleHolder The holder into which the current sample should be written. * @return True if a sample was read. False if there is no current sample. */ public boolean readSample(SampleHolder sampleHolder) { // Write the sample information into the holder and extrasHolder. boolean haveSample = infoQueue.peekSample(sampleHolder, extrasHolder); if (!haveSample) { return false; } // Read encryption data if the sample is encrypted. if (sampleHolder.isEncrypted()) { readEncryptionData(sampleHolder, extrasHolder); } // Write the sample data into the holder. if (sampleHolder.data == null || sampleHolder.data.capacity() < sampleHolder.size) { sampleHolder.replaceBuffer(sampleHolder.size); } if (sampleHolder.data != null) { readData(extrasHolder.offset, sampleHolder.data, sampleHolder.size); } // Advance the read head. long nextOffset = infoQueue.moveToNextSample(); dropDownstreamTo(nextOffset); return true; }
@Override public void seekTo(long positionUs) { mSampleExtractor.seekTo(positionUs); for (SampleHolder holder : mPendingCcSamples) { mCcSamplePool.releaseSample(holder); } mPendingCcSamples.clear(); }
@Override public int readSample(int track, SampleHolder sampleHolder) { if (track == mCea708TextTrackIndex) { if (mCea708TextTrackSelected && !mPendingCcSamples.isEmpty()) { SampleHolder holder = mPendingCcSamples.remove(0); holder.data.flip(); sampleHolder.timeUs = holder.timeUs; sampleHolder.data.put(holder.data); mCcSamplePool.releaseSample(holder); return SampleSource.SAMPLE_READ; } else { return mVideoTrackIndex < 0 || mReachedEos.get(mVideoTrackIndex) ? SampleSource.END_OF_STREAM : SampleSource.NOTHING_READ; } } int result = mSampleExtractor.readSample(track, sampleHolder); switch (result) { case SampleSource.END_OF_STREAM: { mReachedEos.set(track, true); break; } case SampleSource.SAMPLE_READ: { if (mCea708TextTrackSelected && track == mVideoTrackIndex && sampleHolder.data != null) { mCcParser.mayParseClosedCaption(sampleHolder.data, sampleHolder.timeUs); } break; } } return result; }
private int fetchSample(int track, SampleHolder sample, ConditionVariable conditionVariable) { mSampleSourceReader.continueBuffering(track, mCurrentPosition); MediaFormatHolder formatHolder = new MediaFormatHolder(); sample.clearData(); int ret = mSampleSourceReader.readData(track, mCurrentPosition, formatHolder, sample); if (ret == SampleSource.SAMPLE_READ) { if (mCurrentPosition < sample.timeUs) { mCurrentPosition = sample.timeUs; } try { Long lastExtractedPositionUs = mLastExtractedPositionUsMap.get(track); if (lastExtractedPositionUs == null) { mLastExtractedPositionUsMap.put(track, sample.timeUs); } else { mLastExtractedPositionUsMap.put(track, Math.max(lastExtractedPositionUs, sample.timeUs)); } queueSample(track, sample, conditionVariable); } catch (IOException e) { mLastExtractedPositionUsMap.clear(); mMetEos = true; mSampleBuffer.setEos(); } } else if (ret == SampleSource.END_OF_STREAM) { mTrackMetEos[track] = true; for (int i = 0; i < mTrackMetEos.length; ++i) { if (!mTrackMetEos[i]) { break; } if (i == mTrackMetEos.length -1) { mMetEos = true; mSampleBuffer.setEos(); } } } // TODO: Handle SampleSource.FORMAT_READ for dynamic resolution change. b/28169263 return ret; }
private void queueSample(int index, SampleHolder sample, ConditionVariable conditionVariable) throws IOException { long writeStartTimeNs = SystemClock.elapsedRealtimeNanos(); mSampleBuffer.writeSample(index, sample, conditionVariable); // Checks whether the storage has enough bandwidth for recording samples. if (mSampleBuffer.isWriteSpeedSlow(sample.size, SystemClock.elapsedRealtimeNanos() - writeStartTimeNs)) { mSampleBuffer.handleWriteSpeedSlow(); } }
public Cea708TextTrackRenderer(SampleSource source) { mSource = source.register(); mTrackIndex = -1; mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DIRECT); mSampleHolder.ensureSpaceForWrite(DEFAULT_INPUT_BUFFER_SIZE); mFormatHolder = new MediaFormatHolder(); }
public int dequeueSample(SampleHolder sample) { SampleHolder sampleFromQueue = mQueue.poll(); if (sampleFromQueue == null) { return SampleSource.NOTHING_READ; } sample.size = sampleFromQueue.size; sample.flags = sampleFromQueue.flags; sample.timeUs = sampleFromQueue.timeUs; sample.clearData(); sampleFromQueue.data.position(0).limit(sample.size); sample.data.put(sampleFromQueue.data); mSamplePool.releaseSample(sampleFromQueue); return SampleSource.SAMPLE_READ; }
@Override public synchronized int readSample(int track, SampleHolder sampleHolder) { SampleQueue queue = mPlayingSampleQueues[track]; Assert.assertNotNull(queue); int result = queue.dequeueSample(sampleHolder); if (result != SampleSource.SAMPLE_READ && reachedEos()) { return SampleSource.END_OF_STREAM; } return result; }
@Override public void writeSample(int index, SampleHolder sample, ConditionVariable conditionVariable) throws IOException { mSampleChunkIoHelper.writeSample(index, sample, conditionVariable); if (!conditionVariable.block(BUFFER_WRITE_TIMEOUT_MS)) { Log.e(TAG, "Error: Serious delay on writing buffer"); conditionVariable.block(); } }
@Override public int readSample(int track, SampleHolder outSample) { Assertions.checkState(mTrackSelected[track]); maybeReadSample(mReadSampleQueues.get(track), track); int result = mReadSampleQueues.get(track).dequeueSample(outSample); if ((result != SampleSource.SAMPLE_READ && mEos) || mError) { return SampleSource.END_OF_STREAM; } return result; }
/** * Writes a sample. * * @param sample to write * @param nextChunk if this is {@code null} writes at the current SampleChunk, * otherwise close current SampleChunk and writes at this * @throws IOException */ void write(SampleHolder sample, SampleChunk nextChunk) throws IOException { if (nextChunk != null) { if (mChunk == null || mChunk.mNextChunk != null) { throw new IllegalStateException("Requested write for wrong SampleChunk"); } mChunk.closeWrite(nextChunk); mChunk.mChunkCallback.onChunkWrite(mChunk); nextChunk.openWrite(); reset(nextChunk); } mChunk.write(sample, this); }
private SampleHolder read(IoState state) throws IOException { if (mAccessFile == null || state.mChunk != this) { throw new IllegalStateException("Requested read for wrong SampleChunk"); } long offset = state.mCurrentOffset; if (offset >= mWriteOffset) { if (mWriteFinished) { throw new IllegalStateException("Requested read for wrong range"); } else { if (offset != mWriteOffset) { Log.e(TAG, "This should not happen!"); } return null; } } mAccessFile.seek(offset); int size = mAccessFile.readInt(); SampleHolder sample = mSamplePool.acquireSample(size); sample.size = size; sample.flags = mAccessFile.readInt(); sample.timeUs = mAccessFile.readLong(); sample.clearData(); sample.data.put(mAccessFile.getChannel().map(FileChannel.MapMode.READ_ONLY, offset + SAMPLE_HEADER_LENGTH, sample.size)); offset += sample.size + SAMPLE_HEADER_LENGTH; state.mCurrentOffset = offset; return sample; }
private IoParams(int index, long positionUs, SampleHolder sample, ConditionVariable conditionVariable, ConcurrentLinkedQueue<SampleHolder> readSampleBuffer) { this.index = index; this.positionUs = positionUs; this.sample = sample; this.conditionVariable = conditionVariable; this.readSampleBuffer = readSampleBuffer; }
/** * Writes a sample. * * @param index track index * @param sample to write * @param conditionVariable which will be wait until the write is finished * @throws IOException */ public void writeSample(int index, SampleHolder sample, ConditionVariable conditionVariable) throws IOException { if (mErrorNotified) { throw new IOException("Storage I/O error happened"); } conditionVariable.close(); IoParams params = new IoParams(index, 0, sample, conditionVariable, null); mIoHandler.sendMessage(mIoHandler.obtainMessage(MSG_WRITE, params)); }
private void doRead(int index) throws IOException { mIoHandler.removeMessages(MSG_READ, index); if (mHandlerReadSampleBuffers[index].size() >= MAX_READ_BUFFER_SAMPLES) { // If enough samples are buffered, try again few moments later hoping that // buffered samples are consumed. mIoHandler.sendMessageDelayed( mIoHandler.obtainMessage(MSG_READ, index), READ_RESCHEDULING_DELAY_MS); } else { if (mReadIoStates[index].isReadFinished()) { for (int i = 0; i < mTrackCount; ++i) { if (!mReadIoStates[i].isReadFinished()) { return; } } mIoCallback.onIoReachedEos(); return; } SampleHolder sample = mReadIoStates[index].read(); if (sample != null) { mHandlerReadSampleBuffers[index].offer(sample); } else { // Read reached write but write is not finished yet --- wait a few moments to // see if another sample is written. mIoHandler.sendMessageDelayed( mIoHandler.obtainMessage(MSG_READ, index), READ_RESCHEDULING_DELAY_MS); } } }
private void doWrite(IoParams params) throws IOException { try { if (mWriteEnded) { SoftPreconditions.checkState(false); return; } int index = params.index; SampleHolder sample = params.sample; SampleChunk nextChunk = null; if ((sample.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) { if (sample.timeUs > mBufferDurationUs) { mBufferDurationUs = sample.timeUs; } if (sample.timeUs >= mWriteEndPositionUs[index]) { nextChunk = mBufferManager.createNewWriteFile(mIds.get(index), mWriteEndPositionUs[index], mSamplePool); mWriteEndPositionUs[index] = ((sample.timeUs / RecordingSampleBuffer.CHUNK_DURATION_US) + 1) * RecordingSampleBuffer.CHUNK_DURATION_US; } } mWriteIoStates[params.index].write(params.sample, nextChunk); } finally { params.conditionVariable.open(); } }
@Override public int readData(int track, long playbackPositionUs, MediaFormatHolder formatHolder, SampleHolder sampleHolder, boolean onlyReadDiscontinuity) { downstreamPositionUs = playbackPositionUs; if (pendingDiscontinuities[track]) { pendingDiscontinuities[track] = false; return DISCONTINUITY_READ; } if (onlyReadDiscontinuity || isPendingReset()) { return NOTHING_READ; } InternalTrackOutput sampleQueue = sampleQueues.valueAt(track); if (pendingMediaFormat[track]) { formatHolder.format = sampleQueue.getFormat(); formatHolder.drmInitData = drmInitData; pendingMediaFormat[track] = false; return FORMAT_READ; } if (sampleQueue.getSample(sampleHolder)) { boolean decodeOnly = sampleHolder.timeUs < lastSeekPositionUs; sampleHolder.flags |= decodeOnly ? C.SAMPLE_FLAG_DECODE_ONLY : 0; if (havePendingNextSampleUs) { // Set the offset to make the timestamp of this sample equal to pendingNextSampleUs. sampleTimeOffsetUs = pendingNextSampleUs - sampleHolder.timeUs; havePendingNextSampleUs = false; } sampleHolder.timeUs += sampleTimeOffsetUs; return SAMPLE_READ; } if (loadingFinished) { return END_OF_STREAM; } return NOTHING_READ; }
/** * @param allocator An {@link Allocator} from which allocations for sample data can be obtained. */ public DefaultTrackOutput(Allocator allocator) { rollingBuffer = new RollingSampleBuffer(allocator); sampleInfoHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DISABLED); needKeyframe = true; lastReadTimeUs = Long.MIN_VALUE; spliceOutTimeUs = Long.MIN_VALUE; largestParsedTimestampUs = Long.MIN_VALUE; }
/** * Removes the next sample from the head of the queue, writing it into the provided holder. * <p> * The first sample returned is guaranteed to be a keyframe, since any non-keyframe samples * queued prior to the first keyframe are discarded. * * @param holder A {@link SampleHolder} into which the sample should be read. * @return True if a sample was read. False otherwise. */ public boolean getSample(SampleHolder holder) { boolean foundEligibleSample = advanceToEligibleSample(); if (!foundEligibleSample) { return false; } // Write the sample into the holder. rollingBuffer.readSample(holder, format.mimeType); needKeyframe = false; lastReadTimeUs = holder.timeUs; return true; }