/* * Copyright (C) 2012 The Android Open Source Project * * 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 android.media; import android.graphics.ImageFormat; import android.graphics.Rect; import android.media.Image; import android.media.Image.Plane; import android.media.MediaCodecInfo; import android.media.MediaCodecList; import android.media.MediaCrypto; import android.media.MediaFormat; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.view.Surface; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ReadOnlyBufferException; import java.util.Arrays; import java.util.HashMap; import java.util.Map; /** * MediaCodec class can be used to access low-level media codec, i.e. * encoder/decoder components. * *

MediaCodec is generally used like this: *

 * MediaCodec codec = MediaCodec.createDecoderByType(type);
 * codec.configure(format, ...);
 * codec.start();
 *
 * // if API level <= 20, get input and output buffer arrays here
 * ByteBuffer[] inputBuffers = codec.getInputBuffers();
 * ByteBuffer[] outputBuffers = codec.getOutputBuffers();
 * for (;;) {
 *   int inputBufferIndex = codec.dequeueInputBuffer(timeoutUs);
 *   if (inputBufferIndex >= 0) {
 *     // if API level >= 21, get input buffer here
 *     ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferIndex);
 *     // fill inputBuffers[inputBufferIndex] with valid data
 *     ...
 *     codec.queueInputBuffer(inputBufferIndex, ...);
 *   }
 *
 *   int outputBufferIndex = codec.dequeueOutputBuffer(timeoutUs);
 *   if (outputBufferIndex >= 0) {
 *     // if API level >= 21, get output buffer here
 *     ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferIndex);
 *     // outputBuffer is ready to be processed or rendered.
 *     ...
 *     codec.releaseOutputBuffer(outputBufferIndex, ...);
 *   } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
 *     // no needed to handle if API level >= 21 and using getOutputBuffer(int)
 *     outputBuffers = codec.getOutputBuffers();
 *   } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
 *     // Subsequent data will conform to new format.
 *     // can ignore if API level >= 21 and using getOutputFormat(outputBufferIndex)
 *     MediaFormat format = codec.getOutputFormat();
 *     ...
 *   }
 * }
 * codec.stop();
 * codec.release();
 * codec = null;
 * 
* * Each codec maintains a number of input and output buffers that are * referred to by index in API calls. *

* For API levels 20 and below: * The contents of these buffers are represented by the ByteBuffer[] arrays * accessible through {@link #getInputBuffers} and {@link #getOutputBuffers}. *

* After a successful call to {@link #start} the client "owns" neither * input nor output buffers, subsequent calls to {@link #dequeueInputBuffer} * and {@link #dequeueOutputBuffer} then transfer ownership from the codec * to the client.

* The client is not required to resubmit/release buffers immediately * to the codec, the sample code above simply does this for simplicity's sake. * Nonetheless, it is possible that a codec may hold off on generating * output buffers until all outstanding buffers have been * released/resubmitted. *

* Once the client has an input buffer available it can fill it with data * and submit it it to the codec via a call to {@link #queueInputBuffer}. * Do not submit multiple input buffers with the same timestamp (unless * it is codec-specific data marked as such using the flag * {@link #BUFFER_FLAG_CODEC_CONFIG}). *

* The codec in turn will return an output buffer to the client in response * to {@link #dequeueOutputBuffer}. After the output buffer has been processed * a call to {@link #releaseOutputBuffer} will return it to the codec. * If a video surface has been provided in the call to {@link #configure}, * {@link #releaseOutputBuffer} optionally allows rendering of the buffer * to the surface.

* * Input buffers (for decoders) and Output buffers (for encoders) contain * encoded data according to the format's type. For video types this data * is all the encoded data representing a single moment in time, for audio * data this is slightly relaxed in that a buffer may contain multiple * encoded frames of audio. In either case, buffers do not start and end on * arbitrary byte boundaries, this is not a stream of bytes, it's a stream * of access units.

* * Most formats also require the actual data to be prefixed by a number * of buffers containing setup data, or codec specific data, i.e. the * first few buffers submitted to the codec object after starting it must * be codec specific data marked as such using the flag {@link #BUFFER_FLAG_CODEC_CONFIG} * in a call to {@link #queueInputBuffer}. *

* Codec specific data included in the format passed to {@link #configure} * (in ByteBuffer entries with keys "csd-0", "csd-1", ...) is automatically * submitted to the codec, this data MUST NOT be submitted explicitly by the * client. *

* Once the client reaches the end of the input data it signals the end of * the input stream by specifying a flag of {@link #BUFFER_FLAG_END_OF_STREAM} in the call to * {@link #queueInputBuffer}. The codec will continue to return output buffers * until it eventually signals the end of the output stream by specifying * the same flag ({@link #BUFFER_FLAG_END_OF_STREAM}) on the BufferInfo returned in * {@link #dequeueOutputBuffer}. Do not submit additional input buffers after * signaling the end of the input stream, unless the codec has been flushed, * or stopped and restarted. *

*

Seeking & Adaptive Playback Support

* * You can check if a decoder supports adaptive playback via {@link * MediaCodecInfo.CodecCapabilities#isFeatureSupported}. Adaptive playback * is only supported if you configure the codec to decode onto a {@link * android.view.Surface}. * *

For decoders that do not support adaptive playback (including * when not decoding onto a Surface)

* * In order to start decoding data that's not adjacent to previously submitted * data (i.e. after a seek) one must {@link #flush} the decoder. * Any input or output buffers the client may own at the point of the flush are * immediately revoked, i.e. after a call to {@link #flush} the client does not * own any buffers anymore. *

* It is important that the input data after a flush starts at a suitable * stream boundary. The first frame must be able to be decoded completely on * its own (for most codecs this means an I-frame), and that no frames should * refer to frames before that first new frame. * Note that the format of the data submitted after a flush must not change, * flush does not support format discontinuities, * for this a full {@link #stop}, {@link #configure configure()}, {@link #start} * cycle is necessary. * *

For decoders that support adaptive playback

* * In order to start decoding data that's not adjacent to previously submitted * data (i.e. after a seek) it is not necessary to {@link #flush} the * decoder. *

* It is still important that the input data after the discontinuity starts * at a suitable stream boundary (e.g. I-frame), and that no new frames refer * to frames before the first frame of the new input data segment. *

* For some video formats it is also possible to change the picture size * mid-stream. To do this for H.264, the new Sequence Parameter Set (SPS) and * Picture Parameter Set (PPS) values must be packaged together with an * Instantaneous Decoder Refresh (IDR) frame in a single buffer, which then * can be enqueued as a regular input buffer. * The client will receive an {@link #INFO_OUTPUT_FORMAT_CHANGED} return * value from {@link #dequeueOutputBuffer dequeueOutputBuffer()} or * {@link Callback#onOutputBufferAvailable onOutputBufferAvailable()} * just after the picture-size change takes place and before any * frames with the new size have been returned. *

* Be careful when calling {@link #flush} shortly after you have changed * the picture size. If you have not received confirmation of the picture * size change, you will need to repeat the request for the new picture size. * E.g. for H.264 you will need to prepend the PPS/SPS to the new IDR * frame to ensure that the codec receives the picture size change request. * *

States and error handling

* *

During its life, a codec conceptually exists in one of the following states: * Initialized, Configured, Executing, Error, Uninitialized, (omitting transitory states * between them). When created by one of the factory methods, * the codec is in the Initialized state; {@link #configure} brings it to the * Configured state; {@link #start} brings it to the Executing state. * In the Executing state, decoding or encoding occurs through the buffer queue * manipulation described above. The method {@link #stop} * returns the codec to the Initialized state, whereupon it may be configured again, * and {@link #release} brings the codec to the terminal Uninitialized state. When * a codec error occurs, the codec moves to the Error state. Use {@link #reset} to * bring the codec back to the Initialized state, or {@link #release} to move it * to the Uninitialized state. * *

The factory methods * {@link #createByCodecName}, * {@link #createDecoderByType}, * and {@link #createEncoderByType} * throw {@link java.io.IOException} on failure which * the caller must catch or declare to pass up. * MediaCodec methods throw {@link java.lang.IllegalStateException} * when the method is called from a codec state that does not allow it; * this is typically due to incorrect application API usage. * Methods involving secure buffers may throw * {@link MediaCodec.CryptoException#MediaCodec.CryptoException}, which * has further error information obtainable from {@link MediaCodec.CryptoException#getErrorCode}. * *

Internal codec errors result in a {@link MediaCodec.CodecException}, * which may be due to media content corruption, hardware failure, resource exhaustion, * and so forth, even when the application is correctly using the API. * The recommended action when receiving a {@link MediaCodec.CodecException} can be determined by * calling {@link MediaCodec.CodecException#isRecoverable} and * {@link MediaCodec.CodecException#isTransient}. * If {@link MediaCodec.CodecException#isRecoverable} returns true, * then a {@link #stop}, {@link #configure}, and {@link #start} can be performed to recover. * If {@link MediaCodec.CodecException#isTransient} returns true, * then resources are temporarily unavailable and the method may be retried at a later time. * If both {@link MediaCodec.CodecException#isRecoverable} * and {@link MediaCodec.CodecException#isTransient} return false, * then the {@link MediaCodec.CodecException} is fatal and the codec must be * {@link #reset reset} or {@link #release released}. * Both {@link MediaCodec.CodecException#isRecoverable} and * {@link MediaCodec.CodecException#isTransient} do not return true at the same time. */ final public class MediaCodec { /** * Per buffer metadata includes an offset and size specifying * the range of valid data in the associated codec (output) buffer. */ public final static class BufferInfo { /** * Update the buffer metadata information. * * @param newOffset the start-offset of the data in the buffer. * @param newSize the amount of data (in bytes) in the buffer. * @param newTimeUs the presentation timestamp in microseconds. * @param newFlags buffer flags associated with the buffer. This * should be a combination of {@link #BUFFER_FLAG_KEY_FRAME} and * {@link #BUFFER_FLAG_END_OF_STREAM}. */ public void set( int newOffset, int newSize, long newTimeUs, int newFlags) { offset = newOffset; size = newSize; presentationTimeUs = newTimeUs; flags = newFlags; } /** * The start-offset of the data in the buffer. */ public int offset; /** * The amount of data (in bytes) in the buffer. If this is {@code 0}, * the buffer has no data in it and can be discarded. The only * use of a 0-size buffer is to carry the end-of-stream marker. */ public int size; /** * The presentation timestamp in microseconds for the buffer. * This is derived from the presentation timestamp passed in * with the corresponding input buffer. This should be ignored for * a 0-sized buffer. */ public long presentationTimeUs; /** * Buffer flags associated with the buffer. A combination of * {@link #BUFFER_FLAG_KEY_FRAME} and {@link #BUFFER_FLAG_END_OF_STREAM}. * *

Encoded buffers that are key frames are marked with * {@link #BUFFER_FLAG_KEY_FRAME}. * *

The last output buffer corresponding to the input buffer * marked with {@link #BUFFER_FLAG_END_OF_STREAM} will also be marked * with {@link #BUFFER_FLAG_END_OF_STREAM}. In some cases this could * be an empty buffer, whose sole purpose is to carry the end-of-stream * marker. */ public int flags; }; // The follow flag constants MUST stay in sync with their equivalents // in MediaCodec.h ! /** * This indicates that the (encoded) buffer marked as such contains * the data for a key frame. * * @deprecated Use {@link #BUFFER_FLAG_KEY_FRAME} instead. */ public static final int BUFFER_FLAG_SYNC_FRAME = 1; /** * This indicates that the (encoded) buffer marked as such contains * the data for a key frame. */ public static final int BUFFER_FLAG_KEY_FRAME = 1; /** * This indicated that the buffer marked as such contains codec * initialization / codec specific data instead of media data. */ public static final int BUFFER_FLAG_CODEC_CONFIG = 2; /** * This signals the end of stream, i.e. no buffers will be available * after this, unless of course, {@link #flush} follows. */ public static final int BUFFER_FLAG_END_OF_STREAM = 4; private EventHandler mEventHandler; private Callback mCallback; private static final int EVENT_CALLBACK = 1; private static final int EVENT_SET_CALLBACK = 2; private static final int CB_INPUT_AVAILABLE = 1; private static final int CB_OUTPUT_AVAILABLE = 2; private static final int CB_ERROR = 3; private static final int CB_OUTPUT_FORMAT_CHANGE = 4; private class EventHandler extends Handler { private MediaCodec mCodec; public EventHandler(MediaCodec codec, Looper looper) { super(looper); mCodec = codec; } @Override public void handleMessage(Message msg) { switch (msg.what) { case EVENT_CALLBACK: { handleCallback(msg); break; } case EVENT_SET_CALLBACK: { mCallback = (MediaCodec.Callback) msg.obj; break; } default: { break; } } } private void handleCallback(Message msg) { if (mCallback == null) { return; } switch (msg.arg1) { case CB_INPUT_AVAILABLE: { int index = msg.arg2; synchronized(mBufferLock) { validateInputByteBuffer(mCachedInputBuffers, index); } mCallback.onInputBufferAvailable(mCodec, index); break; } case CB_OUTPUT_AVAILABLE: { int index = msg.arg2; BufferInfo info = (MediaCodec.BufferInfo) msg.obj; synchronized(mBufferLock) { validateOutputByteBuffer(mCachedOutputBuffers, index, info); } mCallback.onOutputBufferAvailable( mCodec, index, info); break; } case CB_ERROR: { mCallback.onError(mCodec, (MediaCodec.CodecException) msg.obj); break; } case CB_OUTPUT_FORMAT_CHANGE: { mCallback.onOutputFormatChanged(mCodec, new MediaFormat((Map) msg.obj)); break; } default: { break; } } } } /** * Instantiate a decoder supporting input data of the given mime type. * * The following is a partial list of defined mime types and their semantics: *

* * @param type The mime type of the input data. * @throws IOException if the codec cannot be created. * @throws IllegalArgumentException if type is not a valid mime type. * @throws NullPointerException if type is null. */ public static MediaCodec createDecoderByType(String type) throws IOException { return new MediaCodec(type, true /* nameIsType */, false /* encoder */); } /** * Instantiate an encoder supporting output data of the given mime type. * @param type The desired mime type of the output data. * @throws IOException if the codec cannot be created. * @throws IllegalArgumentException if type is not a valid mime type. * @throws NullPointerException if type is null. */ public static MediaCodec createEncoderByType(String type) throws IOException { return new MediaCodec(type, true /* nameIsType */, true /* encoder */); } /** * If you know the exact name of the component you want to instantiate * use this method to instantiate it. Use with caution. * Likely to be used with information obtained from {@link android.media.MediaCodecList} * @param name The name of the codec to be instantiated. * @throws IOException if the codec cannot be created. * @throws IllegalArgumentException if name is not valid. * @throws NullPointerException if name is null. */ public static MediaCodec createByCodecName(String name) throws IOException { return new MediaCodec( name, false /* nameIsType */, false /* unused */); } private MediaCodec( String name, boolean nameIsType, boolean encoder) { Looper looper; if ((looper = Looper.myLooper()) != null) { mEventHandler = new EventHandler(this, looper); } else if ((looper = Looper.getMainLooper()) != null) { mEventHandler = new EventHandler(this, looper); } else { mEventHandler = null; } mBufferLock = new Object(); native_setup(name, nameIsType, encoder); } @Override protected void finalize() { native_finalize(); } /** * Returns the codec to its initial (Initialized) state. * * Call this if an {@link MediaCodec.CodecException#isRecoverable unrecoverable} * error has occured to reset the codec to its initial state after creation. * * @throws CodecException if an unrecoverable error has occured and the codec * could not be reset. * @throws IllegalStateException if in the Uninitialized state. */ public final void reset() { freeAllTrackedBuffers(); // free buffers first native_reset(); } private native final void native_reset(); /** * Make sure you call this when you're done to free up any opened * component instance instead of relying on the garbage collector * to do this for you at some point in the future. */ public final void release() { freeAllTrackedBuffers(); // free buffers first native_release(); } private native final void native_release(); /** * If this codec is to be used as an encoder, pass this flag. */ public static final int CONFIGURE_FLAG_ENCODE = 1; /** * Configures a component. * * @param format The format of the input data (decoder) or the desired * format of the output data (encoder). * @param surface Specify a surface on which to render the output of this * decoder. * @param crypto Specify a crypto object to facilitate secure decryption * of the media data. * @param flags Specify {@link #CONFIGURE_FLAG_ENCODE} to configure the * component as an encoder. * @throws IllegalArgumentException if the surface has been released (or is invalid), * or the format is unacceptable (e.g. missing a mandatory key), * or the flags are not set properly * (e.g. missing {@link #CONFIGURE_FLAG_ENCODE} for an encoder). * @throws IllegalStateException if not in the Initialized state. */ public void configure( MediaFormat format, Surface surface, MediaCrypto crypto, int flags) { Map formatMap = format.getMap(); String[] keys = null; Object[] values = null; if (format != null) { keys = new String[formatMap.size()]; values = new Object[formatMap.size()]; int i = 0; for (Map.Entry entry: formatMap.entrySet()) { if (entry.getKey().equals(MediaFormat.KEY_AUDIO_SESSION_ID)) { int sessionId = 0; try { sessionId = (Integer)entry.getValue(); } catch (Exception e) { throw new IllegalArgumentException("Wrong Session ID Parameter!"); } keys[i] = "audio-hw-sync"; values[i] = AudioSystem.getAudioHwSyncForSession(sessionId); } else { keys[i] = entry.getKey(); values[i] = entry.getValue(); } ++i; } } native_configure(keys, values, surface, crypto, flags); } private native final void native_setCallback(Callback cb); private native final void native_configure( String[] keys, Object[] values, Surface surface, MediaCrypto crypto, int flags); /** * Requests a Surface to use as the input to an encoder, in place of input buffers. This * may only be called after {@link #configure} and before {@link #start}. *

* The application is responsible for calling release() on the Surface when * done. *

* The Surface must be rendered with a hardware-accelerated API, such as OpenGL ES. * {@link android.view.Surface#lockCanvas(android.graphics.Rect)} may fail or produce * unexpected results. * @throws IllegalStateException if not in the Configured state. */ public native final Surface createInputSurface(); /** * After successfully configuring the component, call {@code start}. *

* Call {@code start} also if the codec is configured in asynchronous mode, * and it has just been flushed, to resume requesting input buffers. * @throws IllegalStateException if not in the Configured state * or just after {@link #flush} for a codec that is configured * in asynchronous mode. * @throws MediaCodec.CodecException upon codec error. Note that some codec errors * for start may be attributed to future method calls. */ public final void start() { native_start(); synchronized(mBufferLock) { cacheBuffers(true /* input */); cacheBuffers(false /* input */); } } private native final void native_start(); /** * Finish the decode/encode session, note that the codec instance * remains active and ready to be {@link #start}ed again. * To ensure that it is available to other client call {@link #release} * and don't just rely on garbage collection to eventually do this for you. * @throws IllegalStateException if in the Uninitialized state. */ public final void stop() { native_stop(); freeAllTrackedBuffers(); if (mEventHandler != null) { mEventHandler.removeMessages(EVENT_CALLBACK); mEventHandler.removeMessages(EVENT_SET_CALLBACK); } } private native final void native_stop(); /** * Flush both input and output ports of the component, all indices * previously returned in calls to {@link #dequeueInputBuffer} and * {@link #dequeueOutputBuffer} become invalid. *

* If codec is configured in asynchronous mode, call {@link #start} * after {@code flush} has returned to resume codec operations. The * codec will not request input buffers until this has happened. *

* If codec is configured in synchronous mode, codec will resume * automatically if an input surface was created. Otherwise, it * will resume when {@link #dequeueInputBuffer} is called. * * @throws IllegalStateException if not in the Executing state. * @throws MediaCodec.CodecException upon codec error. */ public final void flush() { synchronized(mBufferLock) { invalidateByteBuffers(mCachedInputBuffers); invalidateByteBuffers(mCachedOutputBuffers); mDequeuedInputBuffers.clear(); mDequeuedOutputBuffers.clear(); } native_flush(); } private native final void native_flush(); /** * Thrown when an internal codec error occurs. */ public final static class CodecException extends IllegalStateException { CodecException(int errorCode, int actionCode, String detailMessage) { super(detailMessage); mErrorCode = errorCode; mActionCode = actionCode; // TODO get this from codec final String sign = errorCode < 0 ? "neg_" : ""; mDiagnosticInfo = "android.media.MediaCodec.error_" + sign + Math.abs(errorCode); } /** * Returns true if the codec exception is a transient issue, * perhaps due to resource constraints, and that the method * (or encoding/decoding) may be retried at a later time. */ public boolean isTransient() { return mActionCode == ACTION_TRANSIENT; } /** * Returns true if the codec cannot proceed further, * but can be recovered by stopping, configuring, * and starting again. */ public boolean isRecoverable() { return mActionCode == ACTION_RECOVERABLE; } /** * Retrieve the error code associated with a CodecException. * This is opaque diagnostic information and may depend on * hardware or API level. * * @hide */ public int getErrorCode() { return mErrorCode; } /** * Retrieve a developer-readable diagnostic information string * associated with the exception. Do not show this to end-users, * since this string will not be localized or generally * comprehensible to end-users. */ public String getDiagnosticInfo() { return mDiagnosticInfo; } /* Must be in sync with android_media_MediaCodec.cpp */ private final static int ACTION_TRANSIENT = 1; private final static int ACTION_RECOVERABLE = 2; private final String mDiagnosticInfo; private final int mErrorCode; private final int mActionCode; } /** * Thrown when a crypto error occurs while queueing a secure input buffer. */ public final static class CryptoException extends RuntimeException { public CryptoException(int errorCode, String detailMessage) { super(detailMessage); mErrorCode = errorCode; } /** * This indicates that no key has been set to perform the requested * decrypt operation. The operation can be retried after adding * a decryption key. */ public static final int ERROR_NO_KEY = 1; /** * This indicates that the key used for decryption is no longer * valid due to license term expiration. The operation can be retried * after updating the expired keys. */ public static final int ERROR_KEY_EXPIRED = 2; /** * This indicates that a required crypto resource was not able to be * allocated while attempting the requested operation. The operation * can be retried if the app is able to release resources. */ public static final int ERROR_RESOURCE_BUSY = 3; /** * This indicates that the output protection levels supported by the * device are not sufficient to meet the requirements set by the * content owner in the license policy. */ public static final int ERROR_INSUFFICIENT_OUTPUT_PROTECTION = 4; /** * Retrieve the error code associated with a CryptoException */ public int getErrorCode() { return mErrorCode; } private int mErrorCode; } /** * After filling a range of the input buffer at the specified index * submit it to the component. Once an input buffer is queued to * the codec, it MUST NOT be used until it is later retrieved by * {@link #getInputBuffer} in response to a {@link #dequeueInputBuffer} * return value or a {@link Callback#onInputBufferAvailable} * callback. *

* Many decoders require the actual compressed data stream to be * preceded by "codec specific data", i.e. setup data used to initialize * the codec such as PPS/SPS in the case of AVC video or code tables * in the case of vorbis audio. * The class {@link android.media.MediaExtractor} provides codec * specific data as part of * the returned track format in entries named "csd-0", "csd-1" ... *

* These buffers can be submitted directly after {@link #start} or * {@link #flush} by specifying the flag {@link * #BUFFER_FLAG_CODEC_CONFIG}. However, if you configure the * codec with a {@link MediaFormat} containing these keys, they * will be automatically submitted by MediaCodec directly after * start. Therefore, the use of {@link * #BUFFER_FLAG_CODEC_CONFIG} flag is discouraged and is * recommended only for advanced users. *

* To indicate that this is the final piece of input data (or rather that * no more input data follows unless the decoder is subsequently flushed) * specify the flag {@link #BUFFER_FLAG_END_OF_STREAM}. * * @param index The index of a client-owned input buffer previously returned * in a call to {@link #dequeueInputBuffer}. * @param offset The byte offset into the input buffer at which the data starts. * @param size The number of bytes of valid input data. * @param presentationTimeUs The presentation timestamp in microseconds for this * buffer. This is normally the media time at which this * buffer should be presented (rendered). * @param flags A bitmask of flags * {@link #BUFFER_FLAG_CODEC_CONFIG} and {@link #BUFFER_FLAG_END_OF_STREAM}. * While not prohibited, most codecs do not use the * {@link #BUFFER_FLAG_KEY_FRAME} flag for input buffers. * @throws IllegalStateException if not in the Executing state. * @throws MediaCodec.CodecException upon codec error. * @throws CryptoException if a crypto object has been specified in * {@link #configure} */ public final void queueInputBuffer( int index, int offset, int size, long presentationTimeUs, int flags) throws CryptoException { synchronized(mBufferLock) { invalidateByteBuffer(mCachedInputBuffers, index); mDequeuedInputBuffers.remove(index); } try { native_queueInputBuffer( index, offset, size, presentationTimeUs, flags); } catch (CryptoException | IllegalStateException e) { revalidateByteBuffer(mCachedInputBuffers, index); throw e; } } private native final void native_queueInputBuffer( int index, int offset, int size, long presentationTimeUs, int flags) throws CryptoException; // The following mode constants MUST stay in sync with their equivalents // in media/hardware/CryptoAPI.h ! public static final int CRYPTO_MODE_UNENCRYPTED = 0; public static final int CRYPTO_MODE_AES_CTR = 1; /** * Metadata describing the structure of a (at least partially) encrypted * input sample. * A buffer's data is considered to be partitioned into "subSamples", * each subSample starts with a (potentially empty) run of plain, * unencrypted bytes followed by a (also potentially empty) run of * encrypted bytes. * numBytesOfClearData can be null to indicate that all data is encrypted. * This information encapsulates per-sample metadata as outlined in * ISO/IEC FDIS 23001-7:2011 "Common encryption in ISO base media file format files". */ public final static class CryptoInfo { public void set( int newNumSubSamples, int[] newNumBytesOfClearData, int[] newNumBytesOfEncryptedData, byte[] newKey, byte[] newIV, int newMode) { numSubSamples = newNumSubSamples; numBytesOfClearData = newNumBytesOfClearData; numBytesOfEncryptedData = newNumBytesOfEncryptedData; key = newKey; iv = newIV; mode = newMode; } /** * The number of subSamples that make up the buffer's contents. */ public int numSubSamples; /** * The number of leading unencrypted bytes in each subSample. */ public int[] numBytesOfClearData; /** * The number of trailing encrypted bytes in each subSample. */ public int[] numBytesOfEncryptedData; /** * A 16-byte opaque key */ public byte[] key; /** * A 16-byte initialization vector */ public byte[] iv; /** * The type of encryption that has been applied, * see {@link #CRYPTO_MODE_UNENCRYPTED} and {@link #CRYPTO_MODE_AES_CTR}. */ public int mode; @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append(numSubSamples + " subsamples, key ["); String hexdigits = "0123456789abcdef"; for (int i = 0; i < key.length; i++) { builder.append(hexdigits.charAt((key[i] & 0xf0) >> 4)); builder.append(hexdigits.charAt(key[i] & 0x0f)); } builder.append("], iv ["); for (int i = 0; i < key.length; i++) { builder.append(hexdigits.charAt((iv[i] & 0xf0) >> 4)); builder.append(hexdigits.charAt(iv[i] & 0x0f)); } builder.append("], clear "); builder.append(Arrays.toString(numBytesOfClearData)); builder.append(", encrypted "); builder.append(Arrays.toString(numBytesOfEncryptedData)); return builder.toString(); } }; /** * Similar to {@link #queueInputBuffer} but submits a buffer that is * potentially encrypted. * @param index The index of a client-owned input buffer previously returned * in a call to {@link #dequeueInputBuffer}. * @param offset The byte offset into the input buffer at which the data starts. * @param info Metadata required to facilitate decryption, the object can be * reused immediately after this call returns. * @param presentationTimeUs The presentation timestamp in microseconds for this * buffer. This is normally the media time at which this * buffer should be presented (rendered). * @param flags A bitmask of flags * {@link #BUFFER_FLAG_CODEC_CONFIG} and {@link #BUFFER_FLAG_END_OF_STREAM}. * While not prohibited, most codecs do not use the * {@link #BUFFER_FLAG_KEY_FRAME} flag for input buffers. * @throws IllegalStateException if not in the Executing state. * @throws MediaCodec.CodecException upon codec error. * @throws CryptoException if an error occurs while attempting to decrypt the buffer. * An error code associated with the exception helps identify the * reason for the failure. */ public final void queueSecureInputBuffer( int index, int offset, CryptoInfo info, long presentationTimeUs, int flags) throws CryptoException { synchronized(mBufferLock) { invalidateByteBuffer(mCachedInputBuffers, index); mDequeuedInputBuffers.remove(index); } try { native_queueSecureInputBuffer( index, offset, info, presentationTimeUs, flags); } catch (CryptoException | IllegalStateException e) { revalidateByteBuffer(mCachedInputBuffers, index); throw e; } } private native final void native_queueSecureInputBuffer( int index, int offset, CryptoInfo info, long presentationTimeUs, int flags) throws CryptoException; /** * Returns the index of an input buffer to be filled with valid data * or -1 if no such buffer is currently available. * This method will return immediately if timeoutUs == 0, wait indefinitely * for the availability of an input buffer if timeoutUs < 0 or wait up * to "timeoutUs" microseconds if timeoutUs > 0. * @param timeoutUs The timeout in microseconds, a negative timeout indicates "infinite". * @throws IllegalStateException if not in the Executing state, * or codec is configured in asynchronous mode. * @throws MediaCodec.CodecException upon codec error. */ public final int dequeueInputBuffer(long timeoutUs) { int res = native_dequeueInputBuffer(timeoutUs); if (res >= 0) { synchronized(mBufferLock) { validateInputByteBuffer(mCachedInputBuffers, res); } } return res; } private native final int native_dequeueInputBuffer(long timeoutUs); /** * If a non-negative timeout had been specified in the call * to {@link #dequeueOutputBuffer}, indicates that the call timed out. */ public static final int INFO_TRY_AGAIN_LATER = -1; /** * The output format has changed, subsequent data will follow the new * format. {@link #getOutputFormat()} returns the new format. Note, that * you can also use the new {@link #getOutputFormat(int)} method to * get the format for a specific output buffer. This frees you from * having to track output format changes. */ public static final int INFO_OUTPUT_FORMAT_CHANGED = -2; /** * The output buffers have changed, the client must refer to the new * set of output buffers returned by {@link #getOutputBuffers} from * this point on. * * @deprecated This return value can be ignored as {@link * #getOutputBuffers} has been deprecated. Client should * request a current buffer using on of the get-buffer or * get-image methods each time one has been dequeued. */ public static final int INFO_OUTPUT_BUFFERS_CHANGED = -3; /** * Dequeue an output buffer, block at most "timeoutUs" microseconds. * Returns the index of an output buffer that has been successfully * decoded or one of the INFO_* constants below. * @param info Will be filled with buffer meta data. * @param timeoutUs The timeout in microseconds, a negative timeout indicates "infinite". * @throws IllegalStateException if not in the Executing state, * or codec is configured in asynchronous mode. * @throws MediaCodec.CodecException upon codec error. */ public final int dequeueOutputBuffer( BufferInfo info, long timeoutUs) { int res = native_dequeueOutputBuffer(info, timeoutUs); synchronized(mBufferLock) { if (res == INFO_OUTPUT_BUFFERS_CHANGED) { cacheBuffers(false /* input */); } else if (res >= 0) { validateOutputByteBuffer(mCachedOutputBuffers, res, info); } } return res; } private native final int native_dequeueOutputBuffer( BufferInfo info, long timeoutUs); /** * If you are done with a buffer, use this call to return the buffer to * the codec. If you previously specified a surface when configuring this * video decoder you can optionally render the buffer. * * Once an output buffer is released to the codec, it MUST NOT * be used until it is later retrieved by {@link #getOutputBuffer} in response * to a {@link #dequeueOutputBuffer} return value or a * {@link Callback#onOutputBufferAvailable} callback. * * @param index The index of a client-owned output buffer previously returned * from a call to {@link #dequeueOutputBuffer}. * @param render If a valid surface was specified when configuring the codec, * passing true renders this output buffer to the surface. * @throws IllegalStateException if not in the Executing state. * @throws MediaCodec.CodecException upon codec error. */ public final void releaseOutputBuffer(int index, boolean render) { synchronized(mBufferLock) { invalidateByteBuffer(mCachedOutputBuffers, index); mDequeuedOutputBuffers.remove(index); } releaseOutputBuffer(index, render, false /* updatePTS */, 0 /* dummy */); } /** * If you are done with a buffer, use this call to update its surface timestamp * and return it to the codec to render it on the output surface. If you * have not specified an output surface when configuring this video codec, * this call will simply return the buffer to the codec.

* * The timestamp may have special meaning depending on the destination surface. * * * * *
SurfaceView specifics
* If you render your buffer on a {@link android.view.SurfaceView}, * you can use the timestamp to render the buffer at a specific time (at the * VSYNC at or after the buffer timestamp). For this to work, the timestamp * needs to be reasonably close to the current {@link System#nanoTime}. * Currently, this is set as within one (1) second. A few notes: * *
    *
  • the buffer will not be returned to the codec until the timestamp * has passed and the buffer is no longer used by the {@link android.view.Surface}. *
  • buffers are processed sequentially, so you may block subsequent buffers to * be displayed on the {@link android.view.Surface}. This is important if you * want to react to user action, e.g. stop the video or seek. *
  • if multiple buffers are sent to the {@link android.view.Surface} to be * rendered at the same VSYNC, the last one will be shown, and the other ones * will be dropped. *
  • if the timestamp is not "reasonably close" to the current system * time, the {@link android.view.Surface} will ignore the timestamp, and * display the buffer at the earliest feasible time. In this mode it will not * drop frames. *
  • for best performance and quality, call this method when you are about * two VSYNCs' time before the desired render time. For 60Hz displays, this is * about 33 msec. *
*
* * Once an output buffer is released to the codec, it MUST NOT * be used until it is later retrieved by {@link #getOutputBuffer} in response * to a {@link #dequeueOutputBuffer} return value or a * {@link Callback#onOutputBufferAvailable} callback. * * @param index The index of a client-owned output buffer previously returned * from a call to {@link #dequeueOutputBuffer}. * @param renderTimestampNs The timestamp to associate with this buffer when * it is sent to the Surface. * @throws IllegalStateException if not in the Executing state. * @throws MediaCodec.CodecException upon codec error. */ public final void releaseOutputBuffer(int index, long renderTimestampNs) { synchronized(mBufferLock) { invalidateByteBuffer(mCachedOutputBuffers, index); mDequeuedOutputBuffers.remove(index); } releaseOutputBuffer( index, true /* render */, true /* updatePTS */, renderTimestampNs); } private native final void releaseOutputBuffer( int index, boolean render, boolean updatePTS, long timeNs); /** * Signals end-of-stream on input. Equivalent to submitting an empty buffer with * {@link #BUFFER_FLAG_END_OF_STREAM} set. This may only be used with * encoders receiving input from a Surface created by {@link #createInputSurface}. * @throws IllegalStateException if not in the Executing state. * @throws MediaCodec.CodecException upon codec error. */ public native final void signalEndOfInputStream(); /** * Call this after dequeueOutputBuffer signals a format change by returning * {@link #INFO_OUTPUT_FORMAT_CHANGED}. * You can also call this after {@link #configure} returns * successfully to get the output format initially configured * for the codec. Do this to determine what optional * configuration parameters were supported by the codec. * * @throws IllegalStateException if not in the Executing or * Configured state. * @throws MediaCodec.CodecException upon codec error. */ public final MediaFormat getOutputFormat() { return new MediaFormat(getFormatNative(false /* input */)); } /** * Call this after {@link #configure} returns successfully to * get the input format accepted by the codec. Do this to * determine what optional configuration parameters were * supported by the codec. * * @throws IllegalStateException if not in the Executing or * Configured state. * @throws MediaCodec.CodecException upon codec error. */ public final MediaFormat getInputFormat() { return new MediaFormat(getFormatNative(true /* input */)); } /** * Returns the output format for a specific output buffer. * * @param index The index of a client-owned input buffer previously * returned from a call to {@link #dequeueInputBuffer}. * * @return the format for the output buffer, or null if the index * is not a dequeued output buffer. */ public final MediaFormat getOutputFormat(int index) { return new MediaFormat(getOutputFormatNative(index)); } private native final Map getFormatNative(boolean input); private native final Map getOutputFormatNative(int index); // used to track dequeued buffers private static class BufferMap { // various returned representations of the codec buffer private static class CodecBuffer { private Image mImage; private ByteBuffer mByteBuffer; public void free() { if (mByteBuffer != null) { // all of our ByteBuffers are direct java.nio.NioUtils.freeDirectBuffer(mByteBuffer); mByteBuffer = null; } if (mImage != null) { mImage.close(); mImage = null; } } public void setImage(Image image) { free(); mImage = image; } public void setByteBuffer(ByteBuffer buffer) { free(); mByteBuffer = buffer; } } private final Map mMap = new HashMap(); public void remove(int index) { CodecBuffer buffer = mMap.get(index); if (buffer != null) { buffer.free(); mMap.remove(index); } } public void put(int index, ByteBuffer newBuffer) { CodecBuffer buffer = mMap.get(index); if (buffer == null) { // likely buffer = new CodecBuffer(); mMap.put(index, buffer); } buffer.setByteBuffer(newBuffer); } public void put(int index, Image newImage) { CodecBuffer buffer = mMap.get(index); if (buffer == null) { // likely buffer = new CodecBuffer(); mMap.put(index, buffer); } buffer.setImage(newImage); } public void clear() { for (CodecBuffer buffer: mMap.values()) { buffer.free(); } mMap.clear(); } } private ByteBuffer[] mCachedInputBuffers; private ByteBuffer[] mCachedOutputBuffers; private final BufferMap mDequeuedInputBuffers = new BufferMap(); private final BufferMap mDequeuedOutputBuffers = new BufferMap(); final private Object mBufferLock; private final void invalidateByteBuffer( ByteBuffer[] buffers, int index) { if (buffers != null && index >= 0 && index < buffers.length) { ByteBuffer buffer = buffers[index]; if (buffer != null) { buffer.setAccessible(false); } } } private final void validateInputByteBuffer( ByteBuffer[] buffers, int index) { if (buffers != null && index >= 0 && index < buffers.length) { ByteBuffer buffer = buffers[index]; if (buffer != null) { buffer.setAccessible(true); buffer.clear(); } } } private final void revalidateByteBuffer( ByteBuffer[] buffers, int index) { synchronized(mBufferLock) { if (buffers != null && index >= 0 && index < buffers.length) { ByteBuffer buffer = buffers[index]; if (buffer != null) { buffer.setAccessible(true); } } } } private final void validateOutputByteBuffer( ByteBuffer[] buffers, int index, BufferInfo info) { if (buffers != null && index >= 0 && index < buffers.length) { ByteBuffer buffer = buffers[index]; if (buffer != null) { buffer.setAccessible(true); buffer.limit(info.offset + info.size).position(info.offset); } } } private final void invalidateByteBuffers(ByteBuffer[] buffers) { if (buffers != null) { for (ByteBuffer buffer: buffers) { if (buffer != null) { buffer.setAccessible(false); } } } } private final void freeByteBuffer(ByteBuffer buffer) { if (buffer != null /* && buffer.isDirect() */) { // all of our ByteBuffers are direct java.nio.NioUtils.freeDirectBuffer(buffer); } } private final void freeByteBuffers(ByteBuffer[] buffers) { if (buffers != null) { for (ByteBuffer buffer: buffers) { freeByteBuffer(buffer); } } } private final void freeAllTrackedBuffers() { synchronized(mBufferLock) { freeByteBuffers(mCachedInputBuffers); freeByteBuffers(mCachedOutputBuffers); mCachedInputBuffers = null; mCachedOutputBuffers = null; mDequeuedInputBuffers.clear(); mDequeuedOutputBuffers.clear(); } } private final void cacheBuffers(boolean input) { ByteBuffer[] buffers = null; try { buffers = getBuffers(input); invalidateByteBuffers(buffers); } catch (IllegalStateException e) { // we don't get buffers in async mode } if (input) { mCachedInputBuffers = buffers; } else { mCachedOutputBuffers = buffers; } } /** * Retrieve the set of input buffers. Call this after start() * returns. After calling this method, any ByteBuffers * previously returned by an earlier call to this method MUST no * longer be used. * * @deprecated Use the new {@link #getInputBuffer} method instead * each time an input buffer is dequeued. * * Note:As of API 21, dequeued input buffers are * automatically {@link java.nio.Buffer#clear cleared}. * * @throws IllegalStateException if not in the Executing state, * or codec is configured in asynchronous mode. * @throws MediaCodec.CodecException upon codec error. */ public ByteBuffer[] getInputBuffers() { if (mCachedInputBuffers == null) { throw new IllegalStateException(); } // FIXME: check codec status return mCachedInputBuffers; } /** * Retrieve the set of output buffers. Call this after start() * returns and whenever dequeueOutputBuffer signals an output * buffer change by returning {@link * #INFO_OUTPUT_BUFFERS_CHANGED}. After calling this method, any * ByteBuffers previously returned by an earlier call to this * method MUST no longer be used. * * @deprecated Use the new {@link #getOutputBuffer} method instead * each time an output buffer is dequeued. This method is not * supported if codec is configured in asynchronous mode. * * Note:As of API 21, the position and limit of output * buffers that are dequeued will be set to the valid data * range. * * @throws IllegalStateException if not in the Executing state, * or codec is configured in asynchronous mode. * @throws MediaCodec.CodecException upon codec error. */ public ByteBuffer[] getOutputBuffers() { if (mCachedOutputBuffers == null) { throw new IllegalStateException(); } // FIXME: check codec status return mCachedOutputBuffers; } /** * Returns a {@link java.nio.Buffer#clear cleared}, writable ByteBuffer * object for a dequeued input buffer index to contain the input data. * * After calling this method any ByteBuffer or Image object * previously returned for the same input index MUST no longer * be used. * * @param index The index of a client-owned input buffer previously * returned from a call to {@link #dequeueInputBuffer}, * or received via an onInputBufferAvailable callback. * * @return the input buffer, or null if the index is not a dequeued * input buffer, or if the codec is configured for surface input. * * @throws IllegalStateException if not in the Executing state. * @throws MediaCodec.CodecException upon codec error. */ public ByteBuffer getInputBuffer(int index) { ByteBuffer newBuffer = getBuffer(true /* input */, index); synchronized(mBufferLock) { invalidateByteBuffer(mCachedInputBuffers, index); mDequeuedInputBuffers.put(index, newBuffer); } return newBuffer; } /** * Returns a writable Image object for a dequeued input buffer * index to contain the raw input video frame. * * After calling this method any ByteBuffer or Image object * previously returned for the same input index MUST no longer * be used. * * @param index The index of a client-owned input buffer previously * returned from a call to {@link #dequeueInputBuffer}, * or received via an onInputBufferAvailable callback. * * @return the input image, or null if the index is not a * dequeued input buffer, or not a ByteBuffer that contains a * raw image. * * @throws IllegalStateException if not in the Executing state. * @throws MediaCodec.CodecException upon codec error. */ public Image getInputImage(int index) { Image newImage = getImage(true /* input */, index); synchronized(mBufferLock) { invalidateByteBuffer(mCachedInputBuffers, index); mDequeuedInputBuffers.put(index, newImage); } return newImage; } /** * Returns a read-only ByteBuffer for a dequeued output buffer * index. The position and limit of the returned buffer are set * to the valid output data. * * After calling this method, any ByteBuffer or Image object * previously returned for the same output index MUST no longer * be used. * * @param index The index of a client-owned output buffer previously * returned from a call to {@link #dequeueOutputBuffer}, * or received via an onOutputBufferAvailable callback. * * @return the output buffer, or null if the index is not a dequeued * output buffer, or the codec is configured with an output surface. * * @throws IllegalStateException if not in the Executing state. * @throws MediaCodec.CodecException upon codec error. */ public ByteBuffer getOutputBuffer(int index) { ByteBuffer newBuffer = getBuffer(false /* input */, index); synchronized(mBufferLock) { invalidateByteBuffer(mCachedOutputBuffers, index); mDequeuedOutputBuffers.put(index, newBuffer); } return newBuffer; } /** * Returns a read-only Image object for a dequeued output buffer * index that contains the raw video frame. * * After calling this method, any ByteBuffer or Image object previously * returned for the same output index MUST no longer be used. * * @param index The index of a client-owned output buffer previously * returned from a call to {@link #dequeueOutputBuffer}, * or received via an onOutputBufferAvailable callback. * * @return the output image, or null if the index is not a * dequeued output buffer, not a raw video frame, or if the codec * was configured with an output surface. * * @throws IllegalStateException if not in the Executing state. * @throws MediaCodec.CodecException upon codec error. */ public Image getOutputImage(int index) { Image newImage = getImage(false /* input */, index); synchronized(mBufferLock) { invalidateByteBuffer(mCachedOutputBuffers, index); mDequeuedOutputBuffers.put(index, newImage); } return newImage; } /** * The content is scaled to the surface dimensions */ public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = 1; /** * The content is scaled, maintaining its aspect ratio, the whole * surface area is used, content may be cropped */ public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING = 2; /** * If a surface has been specified in a previous call to {@link #configure} * specifies the scaling mode to use. The default is "scale to fit". * @throws IllegalArgumentException if mode is not recognized. * @throws IllegalStateException if in the Uninitialized state. */ public native final void setVideoScalingMode(int mode); /** * Get the component name. If the codec was created by createDecoderByType * or createEncoderByType, what component is chosen is not known beforehand. * @throws IllegalStateException if in the Uninitialized state. */ public native final String getName(); /** * Change a video encoder's target bitrate on the fly. The value is an * Integer object containing the new bitrate in bps. */ public static final String PARAMETER_KEY_VIDEO_BITRATE = "video-bitrate"; /** * Temporarily suspend/resume encoding of input data. While suspended * input data is effectively discarded instead of being fed into the * encoder. This parameter really only makes sense to use with an encoder * in "surface-input" mode, as the client code has no control over the * input-side of the encoder in that case. * The value is an Integer object containing the value 1 to suspend * or the value 0 to resume. */ public static final String PARAMETER_KEY_SUSPEND = "drop-input-frames"; /** * Request that the encoder produce a sync frame "soon". * Provide an Integer with the value 0. */ public static final String PARAMETER_KEY_REQUEST_SYNC_FRAME = "request-sync"; /** * Communicate additional parameter changes to the component instance. * @throws IllegalStateException if in the Uninitialized state. */ public final void setParameters(Bundle params) { if (params == null) { return; } String[] keys = new String[params.size()]; Object[] values = new Object[params.size()]; int i = 0; for (final String key: params.keySet()) { keys[i] = key; values[i] = params.get(key); ++i; } setParameters(keys, values); } /** * Sets an asynchronous callback for actionable MediaCodec events. * * If the client intends to use the component in asynchronous mode, * a valid callback should be provided before {@link #configure} is called. * * When asynchronous callback is enabled, the client should not call * {@link #getInputBuffers}, {@link #getOutputBuffers}, * {@link #dequeueInputBuffer(long)} or {@link #dequeueOutputBuffer(BufferInfo, long)}. *

* Also, {@link #flush} behaves differently in asynchronous mode. After calling * {@code flush}, you must call {@link #start} to "resume" receiving input buffers, * even if an input surface was created. * * @param cb The callback that will run. */ public void setCallback(/* MediaCodec. */ Callback cb) { if (mEventHandler != null) { // set java callback on handler Message msg = mEventHandler.obtainMessage(EVENT_SET_CALLBACK, 0, 0, cb); mEventHandler.sendMessage(msg); // set native handler here, don't post to handler because // it may cause the callback to be delayed and set in a wrong state, // and MediaCodec is already doing it on looper. native_setCallback(cb); } } /** * MediaCodec callback interface. Used to notify the user asynchronously * of various MediaCodec events. */ public static abstract class Callback { /** * Called when an input buffer becomes available. * * @param codec The MediaCodec object. * @param index The index of the available input buffer. */ public abstract void onInputBufferAvailable(MediaCodec codec, int index); /** * Called when an output buffer becomes available. * * @param codec The MediaCodec object. * @param index The index of the available output buffer. * @param info Info regarding the available output buffer {@link MediaCodec.BufferInfo}. */ public abstract void onOutputBufferAvailable(MediaCodec codec, int index, BufferInfo info); /** * Called when the MediaCodec encountered an error * * @param codec The MediaCodec object. * @param e The {@link MediaCodec.CodecException} object describing the error. */ public abstract void onError(MediaCodec codec, CodecException e); /** * Called when the output format has changed * * @param codec The MediaCodec object. * @param format The new output format. */ public abstract void onOutputFormatChanged(MediaCodec codec, MediaFormat format); } private void postEventFromNative( int what, int arg1, int arg2, Object obj) { if (mEventHandler != null) { Message msg = mEventHandler.obtainMessage(what, arg1, arg2, obj); mEventHandler.sendMessage(msg); } } private native final void setParameters(String[] keys, Object[] values); /** * Get the codec info. If the codec was created by createDecoderByType * or createEncoderByType, what component is chosen is not known beforehand, * and thus the caller does not have the MediaCodecInfo. * @throws IllegalStateException if in the Uninitialized state. */ public MediaCodecInfo getCodecInfo() { return MediaCodecList.getInfoFor(getName()); } private native final ByteBuffer[] getBuffers(boolean input); private native final ByteBuffer getBuffer(boolean input, int index); private native final Image getImage(boolean input, int index); private static native final void native_init(); private native final void native_setup( String name, boolean nameIsType, boolean encoder); private native final void native_finalize(); static { System.loadLibrary("media_jni"); native_init(); } private long mNativeContext; /** @hide */ public static class MediaImage extends Image { private final boolean mIsReadOnly; private boolean mIsValid; private final int mWidth; private final int mHeight; private final int mFormat; private long mTimestamp; private final Plane[] mPlanes; private final ByteBuffer mBuffer; private final ByteBuffer mInfo; private final int mXOffset; private final int mYOffset; private final static int TYPE_YUV = 1; public int getFormat() { checkValid(); return mFormat; } public int getHeight() { checkValid(); return mHeight; } public int getWidth() { checkValid(); return mWidth; } public long getTimestamp() { checkValid(); return mTimestamp; } public Plane[] getPlanes() { checkValid(); return Arrays.copyOf(mPlanes, mPlanes.length); } public void close() { if (mIsValid) { java.nio.NioUtils.freeDirectBuffer(mBuffer); mIsValid = false; } } /** * Set the crop rectangle associated with this frame. *

* The crop rectangle specifies the region of valid pixels in the image, * using coordinates in the largest-resolution plane. */ public void setCropRect(Rect cropRect) { if (mIsReadOnly) { throw new ReadOnlyBufferException(); } super.setCropRect(cropRect); } private void checkValid() { if (!mIsValid) { throw new IllegalStateException("Image is already released"); } } private int readInt(ByteBuffer buffer, boolean asLong) { if (asLong) { return (int)buffer.getLong(); } else { return buffer.getInt(); } } public MediaImage( ByteBuffer buffer, ByteBuffer info, boolean readOnly, long timestamp, int xOffset, int yOffset, Rect cropRect) { mFormat = ImageFormat.YUV_420_888; mTimestamp = timestamp; mIsValid = true; mIsReadOnly = buffer.isReadOnly(); mBuffer = buffer.duplicate(); // save offsets and info mXOffset = xOffset; mYOffset = yOffset; mInfo = info; // read media-info. the size of media info can be 80 or 156/160 depending on // whether it was created on a 32- or 64-bit process. See MediaImage if (info.remaining() == 80 || info.remaining() == 156 || info.remaining() == 160) { boolean sizeIsLong = info.remaining() != 80; int type = readInt(info, info.remaining() == 160); if (type != TYPE_YUV) { throw new UnsupportedOperationException("unsupported type: " + type); } int numPlanes = readInt(info, sizeIsLong); if (numPlanes != 3) { throw new RuntimeException("unexpected number of planes: " + numPlanes); } mWidth = readInt(info, sizeIsLong); mHeight = readInt(info, sizeIsLong); if (mWidth < 1 || mHeight < 1) { throw new UnsupportedOperationException( "unsupported size: " + mWidth + "x" + mHeight); } int bitDepth = readInt(info, sizeIsLong); if (bitDepth != 8) { throw new UnsupportedOperationException("unsupported bit depth: " + bitDepth); } mPlanes = new MediaPlane[numPlanes]; for (int ix = 0; ix < numPlanes; ix++) { int planeOffset = readInt(info, sizeIsLong); int colInc = readInt(info, sizeIsLong); int rowInc = readInt(info, sizeIsLong); int horiz = readInt(info, sizeIsLong); int vert = readInt(info, sizeIsLong); if (horiz != vert || horiz != (ix == 0 ? 1 : 2)) { throw new UnsupportedOperationException("unexpected subsampling: " + horiz + "x" + vert + " on plane " + ix); } buffer.clear(); buffer.position(mBuffer.position() + planeOffset + (xOffset / horiz) * colInc + (yOffset / vert) * rowInc); buffer.limit(buffer.position() + Utils.divUp(bitDepth, 8) + (mHeight / vert - 1) * rowInc + (mWidth / horiz - 1) * colInc); mPlanes[ix] = new MediaPlane(buffer.slice(), rowInc, colInc); } } else { throw new UnsupportedOperationException( "unsupported info length: " + info.remaining()); } if (cropRect == null) { cropRect = new Rect(0, 0, mWidth, mHeight); } cropRect.offset(-xOffset, -yOffset); super.setCropRect(cropRect); } private class MediaPlane extends Plane { public MediaPlane(ByteBuffer buffer, int rowInc, int colInc) { mData = buffer; mRowInc = rowInc; mColInc = colInc; } @Override public int getRowStride() { checkValid(); return mRowInc; } @Override public int getPixelStride() { checkValid(); return mColInc; } @Override public ByteBuffer getBuffer() { checkValid(); return mData; } private final int mRowInc; private final int mColInc; private final ByteBuffer mData; } } }