/* * Copyright 2015 Constant Innovations Inc * * 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.constantinnovationsinc.livemultimedia.encoders; import android.app.Application; import android.graphics.ImageFormat; import android.graphics.Rect; import android.graphics.YuvImage; import android.hardware.Camera.Parameters; import android.media.MediaCodec; import android.media.MediaCodec.BufferInfo; import android.media.MediaCodecInfo; import android.media.MediaCodecList; import android.media.MediaFormat; import android.media.MediaMuxer; import android.media.MediaPlayer; import android.os.Debug; import android.os.Environment; import android.os.SystemClock; import android.text.format.Time; import android.util.Log; import com.constantinnovationsinc.livemultimedia.app.MultimediaApp; import com.constantinnovationsinc.livemultimedia.cameras.JellyBeanCamera; import com.constantinnovationsinc.livemultimedia.recorders.AVRecorder; import com.constantinnovationsinc.livemultimedia.utilities.SharedVideoMemory; import java.io.BufferedOutputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.nio.ByteBuffer; /********************************************************************************************** * This calls handles the encoding of the video using the hardware encoder in the GPU * The code that contains the preview window and camera will be moved to separate classes soon * The audio code will be moved as well, it will be changed over to MediaCodec instead * af of using MediarRecorder *********************************************************************************************/ public class GPUEncoder implements Runnable{ private static final String TAG = GPUEncoder.class.getName(); private static final String START_CAPTURE_FRAMES_SOUND = "StartCaptureSound"; private static final String START_ENCODERS_SOUND = "StartEncodersSound"; private static final String MP4_FILE_WRITTEN_SOUND = "MP4FileWrittenSound"; private static final File OUTPUT_FILENAME_DIR = Environment.getExternalStorageDirectory(); private static final int ENCODING_WIDTH = 1280; private static final int ENCODING_HEIGHT = 720; private static final int BITRATE = 6000000; private static final int NUM_CAMERA_PREVIEW_BUFFERS = 2; private static final boolean WORK_AROUND_BUGS = false; // avoid fatal codec bugs // movie length, in frames private static final int NUM_FRAMES = 210; // 10 seconds of video private static int FRAME_RATE = 30; private static final boolean DEBUG_SAVE_FILE = true; // save copy of encoded movie private static final String DEBUG_FILE_NAME_BASE = Environment.getExternalStorageDirectory().getPath() + "/media/"; private static final String MIME_TYPE = "video/avc"; private static final int IFRAME_INTERVAL = 10; // 10 seconds between I-frames private static final String AUDIO_MIME_TYPE = "audio/mp4a-latm"; private String dirImages = null; private byte[] mVvideoFrameData = null; private long realTime = -1; private int mEncodingWidth = -1; private int mEncodingHeight = -1; private long mPreviewWidth = -1; private long mPreviewHeight = -1; private int mImageFormat = -1; // used in memory calculation private int mUsedMegs = -1; private int mColorFormat = -1; // largest color component delta seen (i.e. actual vs. expected) private int mLargestColorDelta = -1; private int mTrackIndex = -1; private long mVideoFrameEncoded = -1; private long mVideoTime = -1; private boolean mMuxerStarted = false; /** Muxer */ private MediaMuxer mMuxer = null; private MediaFormat mNewFormat = null; private MediaCodec mCodec = null; private SharedVideoMemory mSharedMemFile = null; /* -------private complex types --------- */ private BufferInfo mInfo = null; // allocate one of these up front so we don't need to do it every time public BufferInfo mBufferInfo = null; public MediaFormat mFormat = null; public JellyBeanCamera mCamera = null; private static int mVideoTrackIndex = -1; private static int mAudioTrackIndex = -1; /** Encoder status */ private String mOutputFile = null; private boolean mLastTrackProcessed = false; private AVRecorder mRecorder = null; /* Audio */ private int mAudioFrame = 0; private MediaCodec mAudioEncoder; private MediaCodec.BufferInfo mAudioBufferInfo; private MediaPlayer.TrackInfo mAudioTrackInfo; private MediaFormat mAudioFormat; private static final String mAudioCodecName = "audio/mp4a-latm"; private static final int kNumInputBytes = 256 * 1024; private static final long kTimeoutUs = 10000; private MultimediaApp mApp = null; private byte[] mCurrentEncodedAudioData = null; private Boolean mAudioFeatureActive = false; /********************************************************************* * Constructor *********************************************************************/ public GPUEncoder(Application app, AVRecorder recorder ,int videoWidth, int videoHeight) { mEncodingWidth = videoWidth; mEncodingHeight = videoHeight; mRecorder = recorder; mApp = (MultimediaApp)app; } public synchronized void setSharedVideoFramesStore(SharedVideoMemory shared) { if (shared != null) { mSharedMemFile = shared; } } @Override public void run() { } public synchronized void runGPUEncoder() { try { createVideoFormat(); createVideoCodec(); if ( mAudioFeatureActive ) { createAudioFormat(); createAudioCodec(); } createMuxer(); runEncoderLoop(); } catch (Exception e) { Log.e(TAG, e.toString()); } } /**************************************************************************** * prepare() - Starts the show by settings up the preview window and the callbacks * It then starts the video and audio encoding process ****************************************************************************/ public synchronized void prepare( ) { reportMemoryUsage(); } /********************************************************** * release() the encoder which removes the encoder **********************************************************/ public synchronized void release() { if (mCodec != null) { mCodec.stop(); mCodec.release(); mCodec = null; } if ( mAudioFeatureActive ) { if (mAudioEncoder != null) { mAudioEncoder.stop(); mAudioEncoder.release(); mAudioEncoder = null; } } if (mMuxer != null) { mMuxer.stop(); mMuxer.release(); mMuxer = null; } } public synchronized void reportMemoryUsage() { int mFreeMegs = (int) (Runtime.getRuntime().freeMemory() - Debug.getNativeHeapFreeSize()); String freeMegsString = String.format(" - Free Memory %d MB", mFreeMegs); Log.d(TAG, "---------------------------------------------------"); Log.d(TAG, "Memory free to be used in this app in megs is: " + freeMegsString); Log.d(TAG, "---------------------------------------------------"); } public synchronized MediaCodec getCodec() { return mCodec; } public synchronized int getColorFormat() { return mColorFormat; } /******************************************************************* * setupClock() record the time before the encoder loop is entered *******************************************************************/ private synchronized void setupClock() { long bootTime = SystemClock.elapsedRealtime(); } public synchronized void createVideoFormat() { // creat the codec first because you need some info try { mCodec = MediaCodec.createEncoderByType(MIME_TYPE); } catch (IOException e) { e.printStackTrace(); } MediaCodecInfo codecInfo = mCodec.getCodecInfo(); Log.w(TAG, "Codec info Name :" + codecInfo.getName()); Log.w(TAG, "Codec info Encoder? :" + codecInfo.isEncoder()); Log.d(TAG, "Encoding width,height passed to the decoder is: " + String.valueOf(mEncodingWidth) + "," + String.valueOf(mEncodingHeight)); mColorFormat = selectColorFormat(codecInfo, MIME_TYPE); mFormat = MediaFormat.createVideoFormat(MIME_TYPE, mEncodingWidth, mEncodingHeight); mFormat.setInteger(MediaFormat.KEY_BIT_RATE, BITRATE ); mFormat.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE); mFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, mColorFormat); mFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL); } /******************************************************************* * createVideoCodec() creates the video codec which is H264 based ******************************************************************/ public synchronized void createVideoCodec() { try { Log.w(TAG, "----->createVideoCodec()<-----"); mCodec.configure(mFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); mCodec.start(); mBufferInfo = new BufferInfo(); } catch (IllegalStateException e) { Log.e(TAG, "Error in creating video codec failed configuration."); } Log.w(TAG, "----->end createVideoCodec()<-----"); } /******************************************************************************** * Create a MediaMuxer. We can't add the video track and start() the muxer here, * because our MediaFormat doesn't have the Magic Goodies. These can only be * obtained from the encoder after it has started processing data. **********************************************************************************/ @SuppressWarnings("all") private synchronized void createMuxer() { Log.d(TAG, "--->createMuxer()"); if ( isExternalStorageWritable()) { File encodedFile = new File(OUTPUT_FILENAME_DIR, "/movies/EncodedAV" + "-" + mEncodingWidth + "x" + mEncodingHeight + ".mp4"); if (encodedFile.exists()) { encodedFile.delete(); } String outputPath = encodedFile.toString(); int format = MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4; try { mMuxer = new MediaMuxer(outputPath, format); } catch (IOException e) { Log.e(TAG, e.getLocalizedMessage()); } mVideoTrackIndex = -1; mMuxerStarted = false; } } private synchronized void releaseMuxer() { if(mMuxer!=null) { mMuxer.stop(); mMuxer.release(); mMuxer = null; } } public synchronized void startMuxer() { // should happen before receiving buffers, and should only happen once if (mMuxerStarted) { Log.e(TAG, "Encoder status: Something wrong, format changed twice"); } if (mNewFormat != null) { mVideoTrackIndex = mMuxer.addTrack(mNewFormat); if ( mAudioFeatureActive ) { mAudioTrackIndex = mMuxer.addTrack(mAudioFormat); } mMuxer.start(); mMuxerStarted = true; Log.w(TAG, "VideoTrackIndex is: " + mVideoTrackIndex); Log.w(TAG, "AudioTrackIndex is: " + mAudioTrackIndex); } else { Log.e(TAG, "mNewFormat is null in startMuxer()"); } Log.d(TAG, "-------------------------------------------------------------"); Log.d(TAG, "Muxer started!"); Log.d(TAG, "-------------------------------------------------------------"); } public synchronized void createAudioFormat() { if ( mAudioFeatureActive ) { mAudioFormat = new MediaFormat(); mAudioFormat.setString(MediaFormat.KEY_MIME, AUDIO_MIME_TYPE); mAudioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); mAudioFormat.setInteger(MediaFormat.KEY_SAMPLE_RATE, 44100); mAudioFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1); mAudioFormat.setInteger(MediaFormat.KEY_BIT_RATE, 128000); mAudioFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 16384); } } public synchronized void createAudioCodec() { if (mAudioFeatureActive ) { try { mAudioEncoder = MediaCodec.createEncoderByType(mAudioCodecName); } catch (IOException e) { e.printStackTrace(); } mAudioBufferInfo = new MediaCodec.BufferInfo(); try { mAudioEncoder.configure(mAudioFormat, null /* surface */, null /* crypto */, MediaCodec.CONFIGURE_FLAG_ENCODE); } catch (IllegalStateException e) { Log.e(TAG, "codec '" + mAudioCodecName + "' failed configuration."); } mAudioEncoder.start(); } } public synchronized void runEncoderLoop() { // most of the time the encoder sits in this loop // encoding frames until there is no more left // currently it is encoding faster than I can feed it Boolean mEncodingStarted = false; Log.w(TAG, "--->Start to run encoders<----"); if (mSharedMemFile == null) { Log.e(TAG, "SharedMemory file is null in runEncodedLoop!"); return; } try { mEncodingStarted = true; int framecount = mSharedMemFile.getFrameCount(); Log.d(TAG, "video frames stored and waiting to be encoded: " + mSharedMemFile.getFrameCount()); for (int frame = 0; frame < framecount; frame ++) { encodeVideoFromBuffer(frame, mCodec, mColorFormat); } } catch (IndexOutOfBoundsException e) { Log.e(TAG, e.toString()); } finally { Log.w(TAG, "Release the encodwers and the shared memory!!!!!"); try { release(); mRecorder.playSound(MP4_FILE_WRITTEN_SOUND); mRecorder.uploadFile(); Log.w(TAG, "--->All done!!!!!!!!"); } catch (IllegalStateException ex) { Log.e(TAG, ex.toString()); } } } /****************************************************************** * Encode from buffer rather than a surface * @param frameNum = the frame to be encoded ******************************************************************/ @SuppressWarnings("all") public synchronized void encodeVideoFromBuffer(int frameNum, MediaCodec codec, int colorFormat ) { if (mLastTrackProcessed) return; Log.w(TAG, "Encoding video into H264!"); MediaCodec encoder = codec; int encoderColorFormat = colorFormat; // Loop until the output side is done. boolean inputDone = false; boolean encoderDone = false; boolean outputDone = false; final int TIMEOUT_USEC = 10000; mVideoFrameEncoded++; // mVideoTime = mVideoFrameEncoded * 32; // 30 frame a second ByteBuffer[] encoderInputBuffers = encoder.getInputBuffers(); ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers(); int checkIndex = 0; int badFrames = 0; // The size of a frame of video data, in the formats we handle, is stride*sliceHeight // for Y, and (stride/2)*(sliceHeight/2) for each of the Cb and Cr channels. Application // of algebra and assuming that stride==width and sliceHeight==height yields: byte[] frameData = null; byte[] videoFrame; try { frameData = new byte[mEncodingWidth * mEncodingHeight * 3 / 2]; videoFrame = new byte[frameData.length]; if (!mSharedMemFile.isEmpty() ) { mSharedMemFile.getNextFrame(frameNum, videoFrame); } // color correct it System.arraycopy(videoFrame, 0, frameData, 0, videoFrame.length); } catch (OutOfMemoryError e) { Log.e(TAG, e.toString()); } finally { // release it videoFrame = null; } if (!inputDone) { int inputBufIndex = encoder.dequeueInputBuffer(TIMEOUT_USEC); if (inputBufIndex >= 0) { if (mSharedMemFile.isLastFrame()) { // Send an empty frame with the end-of-stream flag set. If we set EOS // on a frame with data, that frame data will be ignored, and the // output will be short one frame. mVideoTime = computePresentationTime(frameNum); encoder.queueInputBuffer(inputBufIndex, 0, 0, mVideoTime, MediaCodec.BUFFER_FLAG_END_OF_STREAM); inputDone = true; Log.w(TAG, "sent input EOS (with zero-length frame)"); } else { ByteBuffer inputBuf = encoderInputBuffers[inputBufIndex]; // the buffer should be sized to hold one full frame if (frameData != null && inputBuf.capacity() >= frameData.length) { Log.w(TAG, "buffer resize to hold correct size"); } else{ Log.e(TAG, "buffer not correct size to fit a frame"); } inputBuf.clear(); inputBuf.put(frameData); mVideoTime = computePresentationTime(frameNum); if (frameData != null) { encoder.queueInputBuffer(inputBufIndex, 0, frameData.length, mVideoTime, 0); } Log.w(TAG, "submitted frame : time " + mVideoFrameEncoded + " : " + mVideoTime + " -> to hardware encoder"); } } else { // either all in use, or we timed out during initial setup Log.d(TAG, "input buffer not available"); } } long rawSize = 0; long encodedSize = 0; if (!encoderDone) { int encoderStatus = encoder.dequeueOutputBuffer(mBufferInfo , TIMEOUT_USEC); switch(encoderStatus) { case MediaCodec.INFO_TRY_AGAIN_LATER: // no output available yet Log.d(TAG, "-------------------------------------------------------------"); Log.d(TAG, "Encoder status: no output from encoder available"); Log.d(TAG, "-------------------------------------------------------------"); break; case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: // not expected for an encoder encoderOutputBuffers = encoder.getOutputBuffers(); Log.d(TAG, "-------------------------------------------------------------"); Log.d(TAG, "Encoder status: encoder output buffers changed"); Log.d(TAG, "-------------------------------------------------------------"); break; case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: // not expected for an encoder mNewFormat = encoder.getOutputFormat(); Log.d(TAG, "-------------------------------------------------------------"); Log.d(TAG, "Encoder status: encoder output format changed: " + mNewFormat.toString()); Log.d(TAG, "-------------------------------------------------------------"); startMuxer(); // reduce the encoded video tracks mVideoFrameEncoded = 0; break; default: ByteBuffer encodedData = encoderOutputBuffers[encoderStatus]; // Codec config info. Only expected on first packet. One way to // handle this is to manually stuff the data into the MediaFormat // and pass that to configure(). if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { // The codec config data was pulled out and fed to the muxer when we got // the INFO_OUTPUT_FORMAT_CHANGED status. Ignore it. Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG"); mBufferInfo.size = 0; } if (encodedData == null) { Log.e(TAG, "encoderOutputBuffer " + encoderStatus + " was null"); return; } else if ( mVideoFrameEncoded == 1) { Log.d(TAG, "-----------------------------------"); Log.d(TAG, "Setting csd-0 on first track"); Log.d(TAG, "-----------------------------------"); mFormat.setByteBuffer("csd-0", encodedData); } // It's usually necessary to adjust the ByteBuffer values to match BufferInfo. if (mBufferInfo.size != 0) { if (encodedData != null && mBufferInfo != null) { encodedData.position(mBufferInfo.offset); encodedData.limit(mBufferInfo.offset + mBufferInfo.size); encodedSize += mBufferInfo.size; Log.w(TAG, "---->Writing Video data using MediaMuxer"); mMuxer.writeSampleData(mVideoTrackIndex, encodedData, mBufferInfo); } if ( mAudioFeatureActive ) { encodeAudio(); // the first frames are configured and are skipped check for that if (mCurrentEncodedAudioData != null) { Log.w(TAG, "---->Writing Audio data using MediaMuxer"); ByteBuffer audioData = ByteBuffer.wrap(mCurrentEncodedAudioData); mMuxer.writeSampleData(mAudioTrackIndex, audioData, mBufferInfo); } mCurrentEncodedAudioData = null; } } // now release the encoder buffer so the MediaCodec can reuse encoder.releaseOutputBuffer(encoderStatus, false); if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { Log.d(TAG, "output EOS"); mLastTrackProcessed = true; } } // END OF ENODER STATUS > 0 } // EMD OF ENCODER LOOP } @SuppressWarnings("all") public synchronized void encodeAudio() { if (!mAudioFeatureActive ) { return; } mAudioFrame++; // not yet encoded. ByteBuffer savedAudioBytes = mApp.pullAudioData(); byte[] audioBytes = new byte[savedAudioBytes.capacity()]; System.arraycopy(savedAudioBytes.array(), 0, audioBytes, 0, audioBytes.length); Log.w(TAG, "Encoding audio frame " + mAudioFrame + " into AAC!"); ByteBuffer[] codecInputBuffers = mAudioEncoder.getInputBuffers(); ByteBuffer[] codecOutputBuffers = mAudioEncoder.getOutputBuffers(); int numBytesSubmitted = 0; boolean doneSubmittingInput = false; int numBytesDequeued = 0; int index; if (!doneSubmittingInput) { index = mAudioEncoder.dequeueInputBuffer(kTimeoutUs /* timeoutUs */); if (index != MediaCodec.INFO_TRY_AGAIN_LATER) { if (numBytesSubmitted >= kNumInputBytes) { mAudioEncoder.queueInputBuffer( index, 0 /* offset */, 0 /* size */, 0 /* timeUs */, MediaCodec.BUFFER_FLAG_END_OF_STREAM); Log.d(TAG, "queued input EOS."); doneSubmittingInput = true; } else { int size = queueInputBuffer(mAudioEncoder, codecInputBuffers, index, audioBytes); numBytesSubmitted += size; Log.d(TAG, "queued " + size + " bytes of input data."); } } MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); index = mAudioEncoder.dequeueOutputBuffer(info, kTimeoutUs /* timeoutUs */); if (index == MediaCodec.INFO_TRY_AGAIN_LATER) { Log.d(TAG, "AUDIO Info try again later!!"); } else if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { Log.d(TAG, "encoder output format changed: Added track index: " + mAudioTrackIndex); } else if (index == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { codecOutputBuffers = mAudioEncoder.getOutputBuffers(); } else { ByteBuffer encodedData = codecOutputBuffers[index]; if (encodedData == null) { Log.e(TAG, "encoderOutputBuffer " + index + " was null in encoding audio!!"); return; } if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) { // The codec config data was pulled out and fed to the muxer when we got // the INFO_OUTPUT_FORMAT_CHANGED status. Ignore it. Log.d(TAG, "ignoring BUFFER_FLAG_CODEC_CONFIG"); info.size = 0; } // Copy the converted audio data mCurrentEncodedAudioData = new byte[info.size]; System.arraycopy(encodedData , 0, mCurrentEncodedAudioData,0, mCurrentEncodedAudioData.length); numBytesDequeued += info.size; if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { Log.w(TAG, "dequeued output EOS."); } Log.w(TAG, "dequeued " + info.size + " bytes of output data."); } } Log.d(TAG, "queued a total of " + numBytesSubmitted + "bytes, " + "dequeued " + numBytesDequeued + " bytes."); int sampleRate = mAudioFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE); int channelCount = mAudioFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT); int inBitrate = sampleRate * channelCount * 16; // bit/sec int outBitrate = mAudioFormat.getInteger(MediaFormat.KEY_BIT_RATE); float desiredRatio = (float)outBitrate / (float)inBitrate; float actualRatio = (float)numBytesDequeued / (float)numBytesSubmitted; if (actualRatio < 0.9 * desiredRatio || actualRatio > 1.1 * desiredRatio) { Log.w(TAG, "desiredRatio = " + desiredRatio + ", actualRatio = " + actualRatio); } } private synchronized int queueInputBuffer( MediaCodec codec, ByteBuffer[] inputBuffers, int index, byte[] audioBuffer) { if ( !mAudioFeatureActive ) { return -1; } ByteBuffer buffer = inputBuffers[index]; buffer.clear(); int size = buffer.limit(); byte[] data = new byte[size]; /// ******** fill with audio data ************ if (data.length >= audioBuffer.length) { System.arraycopy(audioBuffer, 0, data, 0, audioBuffer.length); } buffer.put(data); // ****************************************** codec.queueInputBuffer(index, 0 , size, mVideoTime , 0); return size; } private synchronized void dequeueOutputBuffer( MediaCodec codec, ByteBuffer[] outputBuffers, int index, MediaCodec.BufferInfo info) { if (mAudioFeatureActive) { codec.releaseOutputBuffer(index, false); } } /************************************************************************************* * Returns the first codec capable of encoding the specified MIME type, or null if no * match was found. *************************************************************************************/ @SuppressWarnings("deprecation") private static MediaCodecInfo selectCodec(String mimeType) { int numCodecs = MediaCodecList.getCodecCount(); for (int i = 0; i < numCodecs; i++) { MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); String[] types = codecInfo.getSupportedTypes(); for (String codecType : types) { if (codecType.equalsIgnoreCase(mimeType)) { return codecInfo; } } } return null; } /**************************************************************************************** * Returns a color format that is supported by the codec and by this test code. If no * match is found, this throws a test failure -- the set of formats known to the test * should be expanded for new platforms. ****************************************************************************************/ private static int selectColorFormat(MediaCodecInfo codecInfo, String mimeType) { MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(mimeType); // first print them out for (int i = 0; i < capabilities.colorFormats.length; i++) { int colorFormat = capabilities.colorFormats[i]; Log.w(TAG, "Color format found is :" + colorFormat); } for (int i = 0; i < capabilities.colorFormats.length; i++) { int colorFormat = capabilities.colorFormats[i]; if (isRecognizedFormat(colorFormat)) { return colorFormat; } } Log.e(TAG,"couldn't find a good color format for " + codecInfo.getName() + " / " + mimeType); return 0; // not reached } /********************************************************************************************** * @return - true if this is a color format that this test code understands (i.e. we know how * to read and generate frames in this format). ***********************************************************************************************/ @SuppressWarnings("deprecation") private static boolean isRecognizedFormat(int colorFormat) { boolean flag = false; switch (colorFormat) { // these are the formats we know how to handle for this test case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar: Log.d(TAG, "Color format found: " + "COLOR_FormatYUV420Planar"); flag = true; break; case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar: Log.d(TAG, "Color format found: " + "COLOR_FormatYUV420PackedPlanar"); flag = true; break; case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar: Log.d(TAG, "Color format found: " + "COLOR_FormatYUV420SemiPlanar"); flag = true; break; case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar: Log.d(TAG, "Color format found: " + "COLOR_FormatYUV420PackedSemiPlanar"); flag = true; break; case MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar: Log.d(TAG, "Color format found: " + "COLOR_TI_FormatYUV420PackedSemiPlanar"); flag = true; default: flag = false; } return flag; } /**************************************************************************************** * Returns true if the specified color format is semi-planar YUV. Throws an exception * if the color format is not recognized (e.g. not YUV). ***************************************************************************************/ @SuppressWarnings("deprecation") private static boolean isSemiPlanarYUV(int colorFormat) { switch (colorFormat) { case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar: case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar: return false; case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar: case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar: case MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar: return true; default: throw new RuntimeException("unknown format " + colorFormat); } } /************************************************* * Start the video codec and create the buffers **************************************************/ private void startCodec() { mCodec.start(); } /****************************************************************************************** * Returns true if the actual color value is close to the expected color value. Updates * mLargestColorDelta. * @param actual actual Color * @param expected expected Color * @return is the color expected closew to what is displayed? *******************************************************************************************/ @SuppressWarnings("deprecation") boolean isColorClose(int actual, int expected) { final int MAX_DELTA = 8; int delta = Math.abs(actual - expected); if (delta > mLargestColorDelta) { mLargestColorDelta = delta; } return (delta <= MAX_DELTA); } /*********************************************************** * This code will be replaced by Rest Code or socket code ***********************************************************/ @SuppressWarnings("all") private void saveVideoToWebServer() { try { final Long start = System.nanoTime(); Time now = new Time(); now.setToNow(); final String strDate = now.format2445() + "_" + start; // Create JPEG final Parameters parameters = mCamera.getParameters(); final Thread writeToWebServer = new Thread(new Runnable() { @Override public void run() { String finalPath = dirImages + strDate + "_image.jpg"; String urlRange = "http://10.19.73.148:9000/images/"; HttpURLConnection conn = null; //Log.d(TAG, "Creating filename :" + finalPath); try { URL url = new URL(urlRange); conn = (HttpURLConnection)url.openConnection(); File imageSnapShot = new File(finalPath); imageSnapShot.createNewFile(); FileInputStream fis = new FileInputStream(imageSnapShot); // change frame to JPEG and write to outputstream final ByteArrayOutputStream bos = new ByteArrayOutputStream(); int quality = 100; int imageFormat = parameters.getPreviewFormat(); // this format is for sure with any camera if (imageFormat != ImageFormat.NV21) return; int previewSizeWidth = parameters.getPreviewSize().width; int previewSizeHeight = parameters.getPreviewSize().height; Rect previewSize = new Rect(0, 0, previewSizeWidth, previewSizeHeight); YuvImage image = new YuvImage(mVvideoFrameData, ImageFormat.NV21, previewSizeWidth, previewSizeHeight, null /* strides */); image.compressToJpeg(previewSize, quality, bos); bos.flush(); bos.close(); if (conn != null) { conn.setConnectTimeout(30000); conn.setDefaultUseCaches(true); conn.setDoOutput(true); conn.setChunkedStreamingMode(0); OutputStream os = new BufferedOutputStream(conn.getOutputStream()); os.write(bos.toByteArray()); // // reqEntity.setContentType("binary/octet-stream"); } } catch (IOException ex) { Log.d(TAG, ex.getMessage()); } finally { conn.disconnect(); } } }); writeToWebServer.start(); writeToWebServer.join(); long end = System.nanoTime(); long elapsedTime = end - start; double seconds = (double) elapsedTime / 1000000000.0; Log.d(TAG, "Time elasped and image file written to Network: " + seconds + " [" + strDate + "]"); } catch (Exception e) { Log.e(TAG, e.getMessage()); } } /************************************************************* * Checks if external storage is available for read and write * @return can I write to a sd card **************************************************************/ private boolean isExternalStorageWritable() { String state = Environment.getExternalStorageState(); return Environment.MEDIA_MOUNTED.equals(state); } /************************************************************* /* Checks if external storage is available to at least read *************************************************************/ private boolean isExternalStorageReadable() { String state = Environment.getExternalStorageState(); return Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state); } /***************************************************************** * Generates the presentation time for frame N, in microseconds. * @param frameIndex the index of teh frame * @return long the new time ****************************************************************/ private static long computePresentationTime(int frameIndex) { return 132 + frameIndex * 1000000 / FRAME_RATE; } }