summaryrefslogtreecommitdiffstats
path: root/samples/browseable/BasicRenderScript/src/com.example.android.common.media
diff options
context:
space:
mode:
Diffstat (limited to 'samples/browseable/BasicRenderScript/src/com.example.android.common.media')
-rw-r--r--samples/browseable/BasicRenderScript/src/com.example.android.common.media/CameraHelper.java182
-rw-r--r--samples/browseable/BasicRenderScript/src/com.example.android.common.media/MediaCodecWrapper.java386
2 files changed, 568 insertions, 0 deletions
diff --git a/samples/browseable/BasicRenderScript/src/com.example.android.common.media/CameraHelper.java b/samples/browseable/BasicRenderScript/src/com.example.android.common.media/CameraHelper.java
new file mode 100644
index 000000000..1fa841675
--- /dev/null
+++ b/samples/browseable/BasicRenderScript/src/com.example.android.common.media/CameraHelper.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2013 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 com.example.android.common.media;
+
+import android.annotation.TargetApi;
+import android.hardware.Camera;
+import android.os.Build;
+import android.os.Environment;
+import android.util.Log;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Camera related utilities.
+ */
+public class CameraHelper {
+
+ public static final int MEDIA_TYPE_IMAGE = 1;
+ public static final int MEDIA_TYPE_VIDEO = 2;
+
+ /**
+ * Iterate over supported camera preview sizes to see which one best fits the
+ * dimensions of the given view while maintaining the aspect ratio. If none can,
+ * be lenient with the aspect ratio.
+ *
+ * @param sizes Supported camera preview sizes.
+ * @param w The width of the view.
+ * @param h The height of the view.
+ * @return Best match camera preview size to fit in the view.
+ */
+ public static Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
+ // Use a very small tolerance because we want an exact match.
+ final double ASPECT_TOLERANCE = 0.1;
+ double targetRatio = (double) w / h;
+ if (sizes == null)
+ return null;
+
+ Camera.Size optimalSize = null;
+
+ // Start with max value and refine as we iterate over available preview sizes. This is the
+ // minimum difference between view and camera height.
+ double minDiff = Double.MAX_VALUE;
+
+ // Target view height
+ int targetHeight = h;
+
+ // Try to find a preview size that matches aspect ratio and the target view size.
+ // Iterate over all available sizes and pick the largest size that can fit in the view and
+ // still maintain the aspect ratio.
+ for (Camera.Size size : sizes) {
+ double ratio = (double) size.width / size.height;
+ if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
+ continue;
+ if (Math.abs(size.height - targetHeight) < minDiff) {
+ optimalSize = size;
+ minDiff = Math.abs(size.height - targetHeight);
+ }
+ }
+
+ // Cannot find preview size that matches the aspect ratio, ignore the requirement
+ if (optimalSize == null) {
+ minDiff = Double.MAX_VALUE;
+ for (Camera.Size size : sizes) {
+ if (Math.abs(size.height - targetHeight) < minDiff) {
+ optimalSize = size;
+ minDiff = Math.abs(size.height - targetHeight);
+ }
+ }
+ }
+ return optimalSize;
+ }
+
+ /**
+ * @return the default camera on the device. Return null if there is no camera on the device.
+ */
+ public static Camera getDefaultCameraInstance() {
+ return Camera.open();
+ }
+
+
+ /**
+ * @return the default rear/back facing camera on the device. Returns null if camera is not
+ * available.
+ */
+ public static Camera getDefaultBackFacingCameraInstance() {
+ return getDefaultCamera(Camera.CameraInfo.CAMERA_FACING_BACK);
+ }
+
+ /**
+ * @return the default front facing camera on the device. Returns null if camera is not
+ * available.
+ */
+ public static Camera getDefaultFrontFacingCameraInstance() {
+ return getDefaultCamera(Camera.CameraInfo.CAMERA_FACING_FRONT);
+ }
+
+
+ /**
+ *
+ * @param position Physical position of the camera i.e Camera.CameraInfo.CAMERA_FACING_FRONT
+ * or Camera.CameraInfo.CAMERA_FACING_BACK.
+ * @return the default camera on the device. Returns null if camera is not available.
+ */
+ @TargetApi(Build.VERSION_CODES.GINGERBREAD)
+ private static Camera getDefaultCamera(int position) {
+ // Find the total number of cameras available
+ int mNumberOfCameras = Camera.getNumberOfCameras();
+
+ // Find the ID of the back-facing ("default") camera
+ Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
+ for (int i = 0; i < mNumberOfCameras; i++) {
+ Camera.getCameraInfo(i, cameraInfo);
+ if (cameraInfo.facing == position) {
+ return Camera.open(i);
+
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Creates a media file in the {@code Environment.DIRECTORY_PICTURES} directory. The directory
+ * is persistent and available to other applications like gallery.
+ *
+ * @param type Media type. Can be video or image.
+ * @return A file object pointing to the newly created file.
+ */
+ public static File getOutputMediaFile(int type){
+ // To be safe, you should check that the SDCard is mounted
+ // using Environment.getExternalStorageState() before doing this.
+ if (!Environment.getExternalStorageState().equalsIgnoreCase(Environment.MEDIA_MOUNTED)) {
+ return null;
+ }
+
+ File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
+ Environment.DIRECTORY_PICTURES), "CameraSample");
+ // This location works best if you want the created images to be shared
+ // between applications and persist after your app has been uninstalled.
+
+ // Create the storage directory if it does not exist
+ if (! mediaStorageDir.exists()){
+ if (! mediaStorageDir.mkdirs()) {
+ Log.d("CameraSample", "failed to create directory");
+ return null;
+ }
+ }
+
+ // Create a media file name
+ String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
+ File mediaFile;
+ if (type == MEDIA_TYPE_IMAGE){
+ mediaFile = new File(mediaStorageDir.getPath() + File.separator +
+ "IMG_"+ timeStamp + ".jpg");
+ } else if(type == MEDIA_TYPE_VIDEO) {
+ mediaFile = new File(mediaStorageDir.getPath() + File.separator +
+ "VID_"+ timeStamp + ".mp4");
+ } else {
+ return null;
+ }
+
+ return mediaFile;
+ }
+
+}
diff --git a/samples/browseable/BasicRenderScript/src/com.example.android.common.media/MediaCodecWrapper.java b/samples/browseable/BasicRenderScript/src/com.example.android.common.media/MediaCodecWrapper.java
new file mode 100644
index 000000000..a511221f5
--- /dev/null
+++ b/samples/browseable/BasicRenderScript/src/com.example.android.common.media/MediaCodecWrapper.java
@@ -0,0 +1,386 @@
+/*
+ * Copyright (C) 2013 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 com.example.android.common.media;
+
+import android.media.*;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.Surface;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayDeque;
+import java.util.Queue;
+
+/**
+ * Simplifies the MediaCodec interface by wrapping around the buffer processing operations.
+ */
+public class MediaCodecWrapper {
+
+ // Handler to use for {@code OutputSampleListener} and {code OutputFormatChangedListener}
+ // callbacks
+ private Handler mHandler;
+
+
+ // Callback when media output format changes.
+ public interface OutputFormatChangedListener {
+ void outputFormatChanged(MediaCodecWrapper sender, MediaFormat newFormat);
+ }
+
+ private OutputFormatChangedListener mOutputFormatChangedListener = null;
+
+ /**
+ * Callback for decodes frames. Observers can register a listener for optional stream
+ * of decoded data
+ */
+ public interface OutputSampleListener {
+ void outputSample(MediaCodecWrapper sender, MediaCodec.BufferInfo info, ByteBuffer buffer);
+ }
+
+ /**
+ * The {@link MediaCodec} that is managed by this class.
+ */
+ private MediaCodec mDecoder;
+
+ // References to the internal buffers managed by the codec. The codec
+ // refers to these buffers by index, never by reference so it's up to us
+ // to keep track of which buffer is which.
+ private ByteBuffer[] mInputBuffers;
+ private ByteBuffer[] mOutputBuffers;
+
+ // Indices of the input buffers that are currently available for writing. We'll
+ // consume these in the order they were dequeued from the codec.
+ private Queue<Integer> mAvailableInputBuffers;
+
+ // Indices of the output buffers that currently hold valid data, in the order
+ // they were produced by the codec.
+ private Queue<Integer> mAvailableOutputBuffers;
+
+ // Information about each output buffer, by index. Each entry in this array
+ // is valid if and only if its index is currently contained in mAvailableOutputBuffers.
+ private MediaCodec.BufferInfo[] mOutputBufferInfo;
+
+ // An (optional) stream that will receive decoded data.
+ private OutputSampleListener mOutputSampleListener;
+
+ private MediaCodecWrapper(MediaCodec codec) {
+ mDecoder = codec;
+ codec.start();
+ mInputBuffers = codec.getInputBuffers();
+ mOutputBuffers = codec.getOutputBuffers();
+ mOutputBufferInfo = new MediaCodec.BufferInfo[mOutputBuffers.length];
+ mAvailableInputBuffers = new ArrayDeque<Integer>(mOutputBuffers.length);
+ mAvailableOutputBuffers = new ArrayDeque<Integer>(mInputBuffers.length);
+ }
+
+ /**
+ * Releases resources and ends the encoding/decoding session.
+ */
+ public void stopAndRelease() {
+ mDecoder.stop();
+ mDecoder.release();
+ mDecoder = null;
+ mHandler = null;
+ }
+
+ /**
+ * Getter for the registered {@link OutputFormatChangedListener}
+ */
+ public OutputFormatChangedListener getOutputFormatChangedListener() {
+ return mOutputFormatChangedListener;
+ }
+
+ /**
+ *
+ * @param outputFormatChangedListener the listener for callback.
+ * @param handler message handler for posting the callback.
+ */
+ public void setOutputFormatChangedListener(final OutputFormatChangedListener
+ outputFormatChangedListener, Handler handler) {
+ mOutputFormatChangedListener = outputFormatChangedListener;
+
+ // Making sure we don't block ourselves due to a bad implementation of the callback by
+ // using a handler provided by client.
+ Looper looper;
+ mHandler = handler;
+ if (outputFormatChangedListener != null && mHandler == null) {
+ if ((looper = Looper.myLooper()) != null) {
+ mHandler = new Handler();
+ } else {
+ throw new IllegalArgumentException(
+ "Looper doesn't exist in the calling thread");
+ }
+ }
+ }
+
+ /**
+ * Constructs the {@link MediaCodecWrapper} wrapper object around the video codec.
+ * The codec is created using the encapsulated information in the
+ * {@link MediaFormat} object.
+ *
+ * @param trackFormat The format of the media object to be decoded.
+ * @param surface Surface to render the decoded frames.
+ * @return
+ */
+ public static MediaCodecWrapper fromVideoFormat(final MediaFormat trackFormat,
+ Surface surface) {
+ MediaCodecWrapper result = null;
+ MediaCodec videoCodec = null;
+
+ // BEGIN_INCLUDE(create_codec)
+ final String mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
+
+ // Check to see if this is actually a video mime type. If it is, then create
+ // a codec that can decode this mime type.
+ if (mimeType.contains("video/")) {
+ videoCodec = MediaCodec.createDecoderByType(mimeType);
+ videoCodec.configure(trackFormat, surface, null, 0);
+
+ }
+
+ // If codec creation was successful, then create a wrapper object around the
+ // newly created codec.
+ if (videoCodec != null) {
+ result = new MediaCodecWrapper(videoCodec);
+ }
+ // END_INCLUDE(create_codec)
+
+ return result;
+ }
+
+
+ /**
+ * Write a media sample to the decoder.
+ *
+ * A "sample" here refers to a single atomic access unit in the media stream. The definition
+ * of "access unit" is dependent on the type of encoding used, but it typically refers to
+ * a single frame of video or a few seconds of audio. {@link android.media.MediaExtractor}
+ * extracts data from a stream one sample at a time.
+ *
+ * @param input A ByteBuffer containing the input data for one sample. The buffer must be set
+ * up for reading, with its position set to the beginning of the sample data and its limit
+ * set to the end of the sample data.
+ *
+ * @param presentationTimeUs The time, relative to the beginning of the media stream,
+ * at which this buffer should be rendered.
+ *
+ * @param flags Flags to pass to the decoder. See {@link MediaCodec#queueInputBuffer(int,
+ * int, int, long, int)}
+ *
+ * @throws MediaCodec.CryptoException
+ */
+ public boolean writeSample(final ByteBuffer input,
+ final MediaCodec.CryptoInfo crypto,
+ final long presentationTimeUs,
+ final int flags) throws MediaCodec.CryptoException, WriteException {
+ boolean result = false;
+ int size = input.remaining();
+
+ // check if we have dequed input buffers available from the codec
+ if (size > 0 && !mAvailableInputBuffers.isEmpty()) {
+ int index = mAvailableInputBuffers.remove();
+ ByteBuffer buffer = mInputBuffers[index];
+
+ // we can't write our sample to a lesser capacity input buffer.
+ if (size > buffer.capacity()) {
+ throw new MediaCodecWrapper.WriteException(String.format(
+ "Insufficient capacity in MediaCodec buffer: "
+ + "tried to write %d, buffer capacity is %d.",
+ input.remaining(),
+ buffer.capacity()));
+ }
+
+ buffer.clear();
+ buffer.put(input);
+
+ // Submit the buffer to the codec for decoding. The presentationTimeUs
+ // indicates the position (play time) for the current sample.
+ if (crypto == null) {
+ mDecoder.queueInputBuffer(index, 0, size, presentationTimeUs, flags);
+ } else {
+ mDecoder.queueSecureInputBuffer(index, 0, crypto, presentationTimeUs, flags);
+ }
+ result = true;
+ }
+ return result;
+ }
+
+ static MediaCodec.CryptoInfo cryptoInfo= new MediaCodec.CryptoInfo();
+
+ /**
+ * Write a media sample to the decoder.
+ *
+ * A "sample" here refers to a single atomic access unit in the media stream. The definition
+ * of "access unit" is dependent on the type of encoding used, but it typically refers to
+ * a single frame of video or a few seconds of audio. {@link android.media.MediaExtractor}
+ * extracts data from a stream one sample at a time.
+ *
+ * @param extractor Instance of {@link android.media.MediaExtractor} wrapping the media.
+ *
+ * @param presentationTimeUs The time, relative to the beginning of the media stream,
+ * at which this buffer should be rendered.
+ *
+ * @param flags Flags to pass to the decoder. See {@link MediaCodec#queueInputBuffer(int,
+ * int, int, long, int)}
+ *
+ * @throws MediaCodec.CryptoException
+ */
+ public boolean writeSample(final MediaExtractor extractor,
+ final boolean isSecure,
+ final long presentationTimeUs,
+ int flags) {
+ boolean result = false;
+ boolean isEos = false;
+
+ if (!mAvailableInputBuffers.isEmpty()) {
+ int index = mAvailableInputBuffers.remove();
+ ByteBuffer buffer = mInputBuffers[index];
+
+ // reads the sample from the file using extractor into the buffer
+ int size = extractor.readSampleData(buffer, 0);
+ if (size <= 0) {
+ flags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM;
+ }
+
+ // Submit the buffer to the codec for decoding. The presentationTimeUs
+ // indicates the position (play time) for the current sample.
+ if (!isSecure) {
+ mDecoder.queueInputBuffer(index, 0, size, presentationTimeUs, flags);
+ } else {
+ extractor.getSampleCryptoInfo(cryptoInfo);
+ mDecoder.queueSecureInputBuffer(index, 0, cryptoInfo, presentationTimeUs, flags);
+ }
+
+ result = true;
+ }
+ return result;
+ }
+
+ /**
+ * Performs a peek() operation in the queue to extract media info for the buffer ready to be
+ * released i.e. the head element of the queue.
+ *
+ * @param out_bufferInfo An output var to hold the buffer info.
+ *
+ * @return True, if the peek was successful.
+ */
+ public boolean peekSample(MediaCodec.BufferInfo out_bufferInfo) {
+ // dequeue available buffers and synchronize our data structures with the codec.
+ update();
+ boolean result = false;
+ if (!mAvailableOutputBuffers.isEmpty()) {
+ int index = mAvailableOutputBuffers.peek();
+ MediaCodec.BufferInfo info = mOutputBufferInfo[index];
+ // metadata of the sample
+ out_bufferInfo.set(
+ info.offset,
+ info.size,
+ info.presentationTimeUs,
+ info.flags);
+ result = true;
+ }
+ return result;
+ }
+
+ /**
+ * Processes, releases and optionally renders the output buffer available at the head of the
+ * queue. All observers are notified with a callback. See {@link
+ * OutputSampleListener#outputSample(MediaCodecWrapper, android.media.MediaCodec.BufferInfo,
+ * java.nio.ByteBuffer)}
+ *
+ * @param render True, if the buffer is to be rendered on the {@link Surface} configured
+ *
+ */
+ public void popSample(boolean render) {
+ // dequeue available buffers and synchronize our data structures with the codec.
+ update();
+ if (!mAvailableOutputBuffers.isEmpty()) {
+ int index = mAvailableOutputBuffers.remove();
+
+ if (render && mOutputSampleListener != null) {
+ ByteBuffer buffer = mOutputBuffers[index];
+ MediaCodec.BufferInfo info = mOutputBufferInfo[index];
+ mOutputSampleListener.outputSample(this, info, buffer);
+ }
+
+ // releases the buffer back to the codec
+ mDecoder.releaseOutputBuffer(index, render);
+ }
+ }
+
+ /**
+ * Synchronize this object's state with the internal state of the wrapped
+ * MediaCodec.
+ */
+ private void update() {
+ // BEGIN_INCLUDE(update_codec_state)
+ int index;
+
+ // Get valid input buffers from the codec to fill later in the same order they were
+ // made available by the codec.
+ while ((index = mDecoder.dequeueInputBuffer(0)) != MediaCodec.INFO_TRY_AGAIN_LATER) {
+ mAvailableInputBuffers.add(index);
+ }
+
+
+ // Likewise with output buffers. If the output buffers have changed, start using the
+ // new set of output buffers. If the output format has changed, notify listeners.
+ MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+ while ((index = mDecoder.dequeueOutputBuffer(info, 0)) != MediaCodec.INFO_TRY_AGAIN_LATER) {
+ switch (index) {
+ case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
+ mOutputBuffers = mDecoder.getOutputBuffers();
+ mOutputBufferInfo = new MediaCodec.BufferInfo[mOutputBuffers.length];
+ mAvailableOutputBuffers.clear();
+ break;
+ case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
+ if (mOutputFormatChangedListener != null) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mOutputFormatChangedListener
+ .outputFormatChanged(MediaCodecWrapper.this,
+ mDecoder.getOutputFormat());
+
+ }
+ });
+ }
+ break;
+ default:
+ // Making sure the index is valid before adding to output buffers. We've already
+ // handled INFO_TRY_AGAIN_LATER, INFO_OUTPUT_FORMAT_CHANGED &
+ // INFO_OUTPUT_BUFFERS_CHANGED i.e all the other possible return codes but
+ // asserting index value anyways for future-proofing the code.
+ if(index >= 0) {
+ mOutputBufferInfo[index] = info;
+ mAvailableOutputBuffers.add(index);
+ } else {
+ throw new IllegalStateException("Unknown status from dequeueOutputBuffer");
+ }
+ break;
+ }
+
+ }
+ // END_INCLUDE(update_codec_state)
+
+ }
+
+ private class WriteException extends Throwable {
+ private WriteException(final String detailMessage) {
+ super(detailMessage);
+ }
+ }
+}