/* * Copyright 2015 Mario Guggenberger <mg@protyposis.net> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.protyposis.android.mediaplayer; import android.util.Log; import java.io.IOException; import java.util.ArrayList; import java.util.List; /** * Created by Mario on 13.09.2015. */ class Decoders { private static final String TAG = Decoders.class.getSimpleName(); private List<MediaCodecDecoder> mDecoders; private MediaCodecVideoDecoder mVideoDecoder; private MediaCodecAudioDecoder mAudioDecoder; public Decoders() { mDecoders = new ArrayList<>(); } public void addDecoder(MediaCodecDecoder decoder) { mDecoders.add(decoder); if (decoder instanceof MediaCodecVideoDecoder) { mVideoDecoder = (MediaCodecVideoDecoder) decoder; } else if (decoder instanceof MediaCodecAudioDecoder) { mAudioDecoder = (MediaCodecAudioDecoder) decoder; } } public List<MediaCodecDecoder> getDecoders() { return mDecoders; } public MediaCodecVideoDecoder getVideoDecoder() { return mVideoDecoder; } public MediaCodecAudioDecoder getAudioDecoder() { return mAudioDecoder; } /** * Runs the audio/video decoder loop, optionally until a new frame is available. * The returned frameInfo object keeps metadata of the decoded frame. To render the frame * to the screen and/or dismiss its data, call {@link MediaCodecVideoDecoder#releaseFrame(MediaCodecDecoder.FrameInfo, boolean)} * or {@link MediaCodecVideoDecoder#releaseFrame(MediaCodecDecoder.FrameInfo, long)}. * * @param force force decoding in a loop until a frame becomes available or the EOS is reached * @return a VideoFrameInfo object holding metadata of a decoded video frame or NULL if no frame has been decoded */ public MediaCodecDecoder.FrameInfo decodeFrame(boolean force) { //Log.d(TAG, "decodeFrame"); boolean outputEos = false; while(!outputEos) { int outputEosCount = 0; MediaCodecDecoder.FrameInfo fi; MediaCodecDecoder.FrameInfo vfi = null; for (MediaCodecDecoder decoder : mDecoders) { while((fi = decoder.dequeueDecodedFrame()) != null) { if(decoder == mVideoDecoder) { vfi = fi; break; } else { decoder.renderFrame(fi, 0); } } while (decoder.queueSampleToCodec(false)) {} if(decoder.isOutputEos()) { outputEosCount++; } } if(vfi != null) { // If a video frame has been decoded, return it return vfi; } if(!force) { // If we have not decoded a video frame and we're not forcing decoding until a frame // becomes available, return null. return null; } outputEos = (outputEosCount == mDecoders.size()); } Log.d(TAG, "EOS NULL"); return null; // EOS already reached, no video frame left to return } /** * Releases all decoders. This must be called to free decoder resources when this object is no longer in use. */ public void release() { for (MediaCodecDecoder decoder : mDecoders) { // Catch decoder.release() exceptions to avoid breaking the release loop on the first // exception and leaking unreleased decoders. try { decoder.release(); } catch (Exception e) { Log.e(TAG, "release failed", e); } } mDecoders.clear(); } public void seekTo(MediaPlayer.SeekMode seekMode, long seekTargetTimeUs) throws IOException { for (MediaCodecDecoder decoder : mDecoders) { decoder.seekTo(seekMode, seekTargetTimeUs); } } public void renderFrames() { for (MediaCodecDecoder decoder : mDecoders) { decoder.renderFrame(); } } public void dismissFrames() { for (MediaCodecDecoder decoder : mDecoders) { decoder.dismissFrame(); } } public long getCurrentDecodingPTS() { long minPTS = Long.MAX_VALUE; for (MediaCodecDecoder decoder : mDecoders) { long pts = decoder.getCurrentDecodingPTS(); if(pts != MediaCodecDecoder.PTS_NONE && minPTS > pts) { minPTS = pts; } } return minPTS; } public boolean isEOS() { //return getCurrentDecodingPTS() == MediaCodecDecoder.PTS_EOS; int eosCount = 0; for (MediaCodecDecoder decoder : mDecoders) { if(decoder.isOutputEos()) { eosCount++; } } return eosCount == mDecoders.size(); } public long getCachedDuration() { // Init with the largest possible value... long minCachedDuration = Long.MAX_VALUE; // ...then decrease to the lowest duration. // We always return the lowest value, because if only one decoder has to refill its buffer, // all others have to wait. If one decoder returns -1, this function returns -1 too (which // makes sense because we cannot calculate a meaningful cache duration in this case). for (MediaCodecDecoder decoder : mDecoders) { long cachedDuration = decoder.getCachedDuration(); minCachedDuration = Math.min(cachedDuration, minCachedDuration); } if(minCachedDuration == Long.MAX_VALUE) { // There were no decoders that updated this value, which means we don't have information // on a cached duration, so we return -1 to signal that the information is not available. return -1; } return minCachedDuration; } /** * Returns true only if all decoders have reached the end of stream. */ public boolean hasCacheReachedEndOfStream() { for (MediaCodecDecoder decoder : mDecoders) { if(!decoder.hasCacheReachedEndOfStream()) { return false; } } return true; } }