29 | * The constructor takes a Surface obtained from MediaCodec.createInputSurface(), and uses that 30 | * to create an EGL window surface. Calls to eglSwapBuffers() cause a frame of data to be sent 31 | * to the video encoder. 32 | */ 33 | class InputSurface { 34 | private static final String TAG = "InputSurface"; 35 | private static final int EGL_RECORDABLE_ANDROID = 0x3142; 36 | private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY; 37 | private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT; 38 | private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE; 39 | private Surface mSurface; 40 | /** 41 | * Creates an InputSurface from a Surface. 42 | */ 43 | public InputSurface(Surface surface) { 44 | if (surface == null) { 45 | throw new NullPointerException(); 46 | } 47 | mSurface = surface; 48 | eglSetup(); 49 | } 50 | /** 51 | * Prepares EGL. We want a GLES 2.0 context and a surface that supports recording. 52 | */ 53 | private void eglSetup() { 54 | mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); 55 | if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) { 56 | throw new RuntimeException("unable to get EGL14 display"); 57 | } 58 | int[] version = new int[2]; 59 | if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) { 60 | mEGLDisplay = null; 61 | throw new RuntimeException("unable to initialize EGL14"); 62 | } 63 | // Configure EGL for recordable and OpenGL ES 2.0. We want enough RGB bits 64 | // to minimize artifacts from possible YUV conversion. 65 | int[] attribList = { 66 | EGL14.EGL_RED_SIZE, 8, 67 | EGL14.EGL_GREEN_SIZE, 8, 68 | EGL14.EGL_BLUE_SIZE, 8, 69 | EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT, 70 | EGL_RECORDABLE_ANDROID, 1, 71 | EGL14.EGL_NONE 72 | }; 73 | EGLConfig[] configs = new EGLConfig[1]; 74 | int[] numConfigs = new int[1]; 75 | if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length, 76 | numConfigs, 0)) { 77 | throw new RuntimeException("unable to find RGB888+recordable ES2 EGL config"); 78 | } 79 | // Configure context for OpenGL ES 2.0. 80 | int[] attrib_list = { 81 | EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, 82 | EGL14.EGL_NONE 83 | }; 84 | mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT, 85 | attrib_list, 0); 86 | checkEglError("eglCreateContext"); 87 | if (mEGLContext == null) { 88 | throw new RuntimeException("null context"); 89 | } 90 | // Create a window surface, and attach it to the Surface we received. 91 | int[] surfaceAttribs = { 92 | EGL14.EGL_NONE 93 | }; 94 | mEGLSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, configs[0], mSurface, 95 | surfaceAttribs, 0); 96 | checkEglError("eglCreateWindowSurface"); 97 | if (mEGLSurface == null) { 98 | throw new RuntimeException("surface was null"); 99 | } 100 | } 101 | /** 102 | * Discard all resources held by this class, notably the EGL context. Also releases the 103 | * Surface that was passed to our constructor. 104 | */ 105 | public void release() { 106 | if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { 107 | EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface); 108 | EGL14.eglDestroyContext(mEGLDisplay, mEGLContext); 109 | EGL14.eglReleaseThread(); 110 | EGL14.eglTerminate(mEGLDisplay); 111 | } 112 | mSurface.release(); 113 | mEGLDisplay = EGL14.EGL_NO_DISPLAY; 114 | mEGLContext = EGL14.EGL_NO_CONTEXT; 115 | mEGLSurface = EGL14.EGL_NO_SURFACE; 116 | mSurface = null; 117 | } 118 | /** 119 | * Makes our EGL context and surface current. 120 | */ 121 | public void makeCurrent() { 122 | if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) { 123 | throw new RuntimeException("eglMakeCurrent failed"); 124 | } 125 | } 126 | public void makeUnCurrent() { 127 | if (!EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, 128 | EGL14.EGL_NO_CONTEXT)) { 129 | throw new RuntimeException("eglMakeCurrent failed"); 130 | } 131 | } 132 | /** 133 | * Calls eglSwapBuffers. Use this to "publish" the current frame. 134 | */ 135 | public boolean swapBuffers() { 136 | return EGL14.eglSwapBuffers(mEGLDisplay, mEGLSurface); 137 | } 138 | /** 139 | * Returns the Surface that the MediaCodec receives buffers from. 140 | */ 141 | public Surface getSurface() { 142 | return mSurface; 143 | } 144 | /** 145 | * Queries the surface's width. 146 | */ 147 | public int getWidth() { 148 | int[] value = new int[1]; 149 | EGL14.eglQuerySurface(mEGLDisplay, mEGLSurface, EGL14.EGL_WIDTH, value, 0); 150 | return value[0]; 151 | } 152 | /** 153 | * Queries the surface's height. 154 | */ 155 | public int getHeight() { 156 | int[] value = new int[1]; 157 | EGL14.eglQuerySurface(mEGLDisplay, mEGLSurface, EGL14.EGL_HEIGHT, value, 0); 158 | return value[0]; 159 | } 160 | /** 161 | * Sends the presentation time stamp to EGL. Time is expressed in nanoseconds. 162 | */ 163 | public void setPresentationTime(long nsecs) { 164 | EGLExt.eglPresentationTimeANDROID(mEGLDisplay, mEGLSurface, nsecs); 165 | } 166 | /** 167 | * Checks for EGL errors. 168 | */ 169 | private void checkEglError(String msg) { 170 | int error; 171 | if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) { 172 | throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error)); 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /lib/src/main/java/net/ypresto/androidtranscoder/engine/InvalidOutputFormatException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Yuya Tanaka 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.ypresto.androidtranscoder.engine; 17 | 18 | public class InvalidOutputFormatException extends RuntimeException { 19 | public InvalidOutputFormatException(String detailMessage) { 20 | super(detailMessage); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/src/main/java/net/ypresto/androidtranscoder/engine/MediaFormatValidator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Yuya Tanaka 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.ypresto.androidtranscoder.engine; 17 | 18 | import android.media.MediaFormat; 19 | 20 | import net.ypresto.androidtranscoder.format.MediaFormatExtraConstants; 21 | 22 | class MediaFormatValidator { 23 | 24 | public static void validateVideoOutputFormat(MediaFormat format) { 25 | String mime = format.getString(MediaFormat.KEY_MIME); 26 | // Refer: http://developer.android.com/guide/appendix/media-formats.html#core 27 | // Refer: http://en.wikipedia.org/wiki/MPEG-4_Part_14#Data_streams 28 | if (!MediaFormatExtraConstants.MIMETYPE_VIDEO_AVC.equals(mime)) { 29 | throw new InvalidOutputFormatException("Video codecs other than AVC is not supported, actual mime type: " + mime); 30 | } 31 | } 32 | 33 | public static void validateAudioOutputFormat(MediaFormat format) { 34 | String mime = format.getString(MediaFormat.KEY_MIME); 35 | if (!MediaFormatExtraConstants.MIMETYPE_AUDIO_AAC.equals(mime)) { 36 | throw new InvalidOutputFormatException("Audio codecs other than AAC is not supported, actual mime type: " + mime); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/src/main/java/net/ypresto/androidtranscoder/engine/MediaTranscoderEngine.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 Yuya Tanaka 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package net.ypresto.androidtranscoder.engine; 17 | 18 | import android.media.MediaExtractor; 19 | import android.media.MediaFormat; 20 | import android.media.MediaMetadataRetriever; 21 | import android.media.MediaMuxer; 22 | import android.os.Build; 23 | import android.util.Log; 24 | 25 | import net.ypresto.androidtranscoder.BuildConfig; 26 | import net.ypresto.androidtranscoder.format.MediaFormatStrategy; 27 | import net.ypresto.androidtranscoder.utils.ISO6709LocationParser; 28 | import net.ypresto.androidtranscoder.utils.MediaExtractorUtils; 29 | 30 | import java.io.FileDescriptor; 31 | import java.io.IOException; 32 | 33 | /** 34 | * Internal engine, do not use this directly. 35 | */ 36 | // TODO: treat encrypted data 37 | public class MediaTranscoderEngine { 38 | private static final String TAG = "MediaTranscoderEngine"; 39 | private static final double PROGRESS_UNKNOWN = -1.0; 40 | private static final long SLEEP_TO_WAIT_TRACK_TRANSCODERS = 10; 41 | private static final long PROGRESS_INTERVAL_STEPS = 10; 42 | private FileDescriptor mInputFileDescriptor; 43 | private TrackTranscoder mVideoTrackTranscoder; 44 | private TrackTranscoder mAudioTrackTranscoder; 45 | private MediaExtractor mExtractor; 46 | private MediaMuxer mMuxer; 47 | private volatile double mProgress; 48 | private ProgressCallback mProgressCallback; 49 | private long mDurationUs; 50 | 51 | /** 52 | * Do not use this constructor unless you know what you are doing. 53 | */ 54 | public MediaTranscoderEngine() { 55 | } 56 | 57 | public void setDataSource(FileDescriptor fileDescriptor) { 58 | mInputFileDescriptor = fileDescriptor; 59 | } 60 | 61 | public ProgressCallback getProgressCallback() { 62 | return mProgressCallback; 63 | } 64 | 65 | public void setProgressCallback(ProgressCallback progressCallback) { 66 | mProgressCallback = progressCallback; 67 | } 68 | 69 | /** 70 | * NOTE: This method is thread safe. 71 | */ 72 | public double getProgress() { 73 | return mProgress; 74 | } 75 | 76 | /** 77 | * Run video transcoding. Blocks current thread. 78 | * Audio data will not be transcoded; original stream will be wrote to output file. 79 | * 80 | * @param outputPath File path to output transcoded video file. 81 | * @param formatStrategy Output format strategy. 82 | * @throws IOException when input or output file could not be opened. 83 | * @throws InvalidOutputFormatException when output format is not supported. 84 | * @throws InterruptedException when cancel to transcode. 85 | */ 86 | public void transcodeVideo(String outputPath, MediaFormatStrategy formatStrategy) throws IOException, InterruptedException { 87 | if (outputPath == null) { 88 | throw new NullPointerException("Output path cannot be null."); 89 | } 90 | if (mInputFileDescriptor == null) { 91 | throw new IllegalStateException("Data source is not set."); 92 | } 93 | try { 94 | // NOTE: use single extractor to keep from running out audio track fast. 95 | mExtractor = new MediaExtractor(); 96 | mExtractor.setDataSource(mInputFileDescriptor); 97 | mMuxer = new MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); 98 | setupMetadata(); 99 | setupTrackTranscoders(formatStrategy); 100 | runPipelines(); 101 | mMuxer.stop(); 102 | } finally { 103 | try { 104 | if (mVideoTrackTranscoder != null) { 105 | mVideoTrackTranscoder.release(); 106 | mVideoTrackTranscoder = null; 107 | } 108 | if (mAudioTrackTranscoder != null) { 109 | mAudioTrackTranscoder.release(); 110 | mAudioTrackTranscoder = null; 111 | } 112 | if (mExtractor != null) { 113 | mExtractor.release(); 114 | mExtractor = null; 115 | } 116 | } catch (RuntimeException e) { 117 | // Too fatal to make alive the app, because it may leak native resources. 118 | //noinspection ThrowFromFinallyBlock 119 | throw new Error("Could not shutdown extractor, codecs and muxer pipeline.", e); 120 | } 121 | try { 122 | if (mMuxer != null) { 123 | mMuxer.release(); 124 | mMuxer = null; 125 | } 126 | } catch (RuntimeException e) { 127 | Log.e(TAG, "Failed to release muxer.", e); 128 | } 129 | } 130 | } 131 | 132 | private void setupMetadata() throws IOException { 133 | MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever(); 134 | mediaMetadataRetriever.setDataSource(mInputFileDescriptor); 135 | 136 | String rotationString = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION); 137 | try { 138 | mMuxer.setOrientationHint(Integer.parseInt(rotationString)); 139 | } catch (NumberFormatException e) { 140 | // skip 141 | } 142 | 143 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 144 | String locationString = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION); 145 | if (locationString != null) { 146 | float[] location = new ISO6709LocationParser().parse(locationString); 147 | if (location != null) { 148 | mMuxer.setLocation(location[0], location[1]); 149 | } else { 150 | Log.d(TAG, "Failed to parse the location metadata: " + locationString); 151 | } 152 | } 153 | } 154 | 155 | try { 156 | mDurationUs = Long.parseLong(mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)) * 1000; 157 | } catch (NumberFormatException e) { 158 | mDurationUs = -1; 159 | } 160 | Log.d(TAG, "Duration (us): " + mDurationUs); 161 | } 162 | 163 | private void setupTrackTranscoders(MediaFormatStrategy formatStrategy) { 164 | MediaExtractorUtils.TrackResult trackResult = MediaExtractorUtils.getFirstVideoAndAudioTrack(mExtractor); 165 | MediaFormat videoOutputFormat = formatStrategy.createVideoOutputFormat(trackResult.mVideoTrackFormat); 166 | MediaFormat audioOutputFormat = formatStrategy.createAudioOutputFormat(trackResult.mAudioTrackFormat); 167 | if (videoOutputFormat == null && audioOutputFormat == null) { 168 | throw new InvalidOutputFormatException("MediaFormatStrategy returned pass-through for both video and audio. No transcoding is necessary."); 169 | } 170 | QueuedMuxer queuedMuxer = new QueuedMuxer(mMuxer, new QueuedMuxer.Listener() { 171 | @Override 172 | public void onDetermineOutputFormat() { 173 | MediaFormatValidator.validateVideoOutputFormat(mVideoTrackTranscoder.getDeterminedFormat()); 174 | MediaFormatValidator.validateAudioOutputFormat(mAudioTrackTranscoder.getDeterminedFormat()); 175 | } 176 | }); 177 | 178 | if (videoOutputFormat == null) { 179 | mVideoTrackTranscoder = new PassThroughTrackTranscoder(mExtractor, trackResult.mVideoTrackIndex, queuedMuxer, QueuedMuxer.SampleType.VIDEO); 180 | } else { 181 | mVideoTrackTranscoder = new VideoTrackTranscoder(mExtractor, trackResult.mVideoTrackIndex, videoOutputFormat, queuedMuxer); 182 | } 183 | mVideoTrackTranscoder.setup(); 184 | if (audioOutputFormat == null) { 185 | mAudioTrackTranscoder = new PassThroughTrackTranscoder(mExtractor, trackResult.mAudioTrackIndex, queuedMuxer, QueuedMuxer.SampleType.AUDIO); 186 | } else { 187 | mAudioTrackTranscoder = new AudioTrackTranscoder(mExtractor, trackResult.mAudioTrackIndex, audioOutputFormat, queuedMuxer); 188 | } 189 | mAudioTrackTranscoder.setup(); 190 | mExtractor.selectTrack(trackResult.mVideoTrackIndex); 191 | mExtractor.selectTrack(trackResult.mAudioTrackIndex); 192 | } 193 | 194 | private void runPipelines() throws InterruptedException { 195 | long loopCount = 0; 196 | if (mDurationUs <= 0) { 197 | double progress = PROGRESS_UNKNOWN; 198 | mProgress = progress; 199 | if (mProgressCallback != null) mProgressCallback.onProgress(progress); // unknown 200 | } 201 | while (!(mVideoTrackTranscoder.isFinished() && mAudioTrackTranscoder.isFinished())) { 202 | boolean stepped = mVideoTrackTranscoder.stepPipeline() 203 | || mAudioTrackTranscoder.stepPipeline(); 204 | loopCount++; 205 | if (mDurationUs > 0 && loopCount % PROGRESS_INTERVAL_STEPS == 0) { 206 | double videoProgress = mVideoTrackTranscoder.isFinished() ? 1.0 : Math.min(1.0, (double) mVideoTrackTranscoder.getWrittenPresentationTimeUs() / mDurationUs); 207 | double audioProgress = mAudioTrackTranscoder.isFinished() ? 1.0 : Math.min(1.0, (double) mAudioTrackTranscoder.getWrittenPresentationTimeUs() / mDurationUs); 208 | double progress = (videoProgress + audioProgress) / 2.0; 209 | mProgress = progress; 210 | if (mProgressCallback != null) mProgressCallback.onProgress(progress); 211 | } 212 | if (!stepped) { 213 | Thread.sleep(SLEEP_TO_WAIT_TRACK_TRANSCODERS); 214 | } 215 | } 216 | } 217 | 218 | public interface ProgressCallback { 219 | /** 220 | * Called to notify progress. Same thread which initiated transcode is used. 221 | * 222 | * @param progress Progress in [0.0, 1.0] range, or negative value if progress is unknown. 223 | */ 224 | void onProgress(double progress); 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /lib/src/main/java/net/ypresto/androidtranscoder/engine/OutputSurface.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | // from: https://android.googlesource.com/platform/cts/+/lollipop-release/tests/tests/media/src/android/media/cts/OutputSurface.java 17 | // blob: fc8ad9cd390c5c311f015d3b7c1359e4d295bc52 18 | // modified: change TIMEOUT_MS from 500 to 10000 19 | package net.ypresto.androidtranscoder.engine; 20 | import android.graphics.SurfaceTexture; 21 | import android.opengl.EGL14; 22 | import android.opengl.EGLConfig; 23 | import android.opengl.EGLContext; 24 | import android.opengl.EGLDisplay; 25 | import android.opengl.EGLSurface; 26 | import android.util.Log; 27 | import android.view.Surface; 28 | /** 29 | * Holds state associated with a Surface used for MediaCodec decoder output. 30 | *
31 | * The (width,height) constructor for this class will prepare GL, create a SurfaceTexture, 32 | * and then create a Surface for that SurfaceTexture. The Surface can be passed to 33 | * MediaCodec.configure() to receive decoder output. When a frame arrives, we latch the 34 | * texture with updateTexImage, then render the texture with GL to a pbuffer. 35 | *
36 | * The no-arg constructor skips the GL preparation step and doesn't allocate a pbuffer. 37 | * Instead, it just creates the Surface and SurfaceTexture, and when a frame arrives 38 | * we just draw it on whatever surface is current. 39 | *
40 | * By default, the Surface will be using a BufferQueue in asynchronous mode, so we
41 | * can potentially drop frames.
42 | */
43 | class OutputSurface implements SurfaceTexture.OnFrameAvailableListener {
44 | private static final String TAG = "OutputSurface";
45 | private static final boolean VERBOSE = false;
46 | private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY;
47 | private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT;
48 | private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE;
49 | private SurfaceTexture mSurfaceTexture;
50 | private Surface mSurface;
51 | private Object mFrameSyncObject = new Object(); // guards mFrameAvailable
52 | private boolean mFrameAvailable;
53 | private TextureRender mTextureRender;
54 | /**
55 | * Creates an OutputSurface backed by a pbuffer with the specifed dimensions. The new
56 | * EGL context and surface will be made current. Creates a Surface that can be passed
57 | * to MediaCodec.configure().
58 | */
59 | public OutputSurface(int width, int height) {
60 | if (width <= 0 || height <= 0) {
61 | throw new IllegalArgumentException();
62 | }
63 | eglSetup(width, height);
64 | makeCurrent();
65 | setup();
66 | }
67 | /**
68 | * Creates an OutputSurface using the current EGL context (rather than establishing a
69 | * new one). Creates a Surface that can be passed to MediaCodec.configure().
70 | */
71 | public OutputSurface() {
72 | setup();
73 | }
74 | /**
75 | * Creates instances of TextureRender and SurfaceTexture, and a Surface associated
76 | * with the SurfaceTexture.
77 | */
78 | private void setup() {
79 | mTextureRender = new TextureRender();
80 | mTextureRender.surfaceCreated();
81 | // Even if we don't access the SurfaceTexture after the constructor returns, we
82 | // still need to keep a reference to it. The Surface doesn't retain a reference
83 | // at the Java level, so if we don't either then the object can get GCed, which
84 | // causes the native finalizer to run.
85 | if (VERBOSE) Log.d(TAG, "textureID=" + mTextureRender.getTextureId());
86 | mSurfaceTexture = new SurfaceTexture(mTextureRender.getTextureId());
87 | // This doesn't work if OutputSurface is created on the thread that CTS started for
88 | // these test cases.
89 | //
90 | // The CTS-created thread has a Looper, and the SurfaceTexture constructor will
91 | // create a Handler that uses it. The "frame available" message is delivered
92 | // there, but since we're not a Looper-based thread we'll never see it. For
93 | // this to do anything useful, OutputSurface must be created on a thread without
94 | // a Looper, so that SurfaceTexture uses the main application Looper instead.
95 | //
96 | // Java language note: passing "this" out of a constructor is generally unwise,
97 | // but we should be able to get away with it here.
98 | mSurfaceTexture.setOnFrameAvailableListener(this);
99 | mSurface = new Surface(mSurfaceTexture);
100 | }
101 | /**
102 | * Prepares EGL. We want a GLES 2.0 context and a surface that supports pbuffer.
103 | */
104 | private void eglSetup(int width, int height) {
105 | mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
106 | if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
107 | throw new RuntimeException("unable to get EGL14 display");
108 | }
109 | int[] version = new int[2];
110 | if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
111 | mEGLDisplay = null;
112 | throw new RuntimeException("unable to initialize EGL14");
113 | }
114 | // Configure EGL for pbuffer and OpenGL ES 2.0. We want enough RGB bits
115 | // to be able to tell if the frame is reasonable.
116 | int[] attribList = {
117 | EGL14.EGL_RED_SIZE, 8,
118 | EGL14.EGL_GREEN_SIZE, 8,
119 | EGL14.EGL_BLUE_SIZE, 8,
120 | EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
121 | EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT,
122 | EGL14.EGL_NONE
123 | };
124 | EGLConfig[] configs = new EGLConfig[1];
125 | int[] numConfigs = new int[1];
126 | if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length,
127 | numConfigs, 0)) {
128 | throw new RuntimeException("unable to find RGB888+recordable ES2 EGL config");
129 | }
130 | // Configure context for OpenGL ES 2.0.
131 | int[] attrib_list = {
132 | EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
133 | EGL14.EGL_NONE
134 | };
135 | mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT,
136 | attrib_list, 0);
137 | checkEglError("eglCreateContext");
138 | if (mEGLContext == null) {
139 | throw new RuntimeException("null context");
140 | }
141 | // Create a pbuffer surface. By using this for output, we can use glReadPixels
142 | // to test values in the output.
143 | int[] surfaceAttribs = {
144 | EGL14.EGL_WIDTH, width,
145 | EGL14.EGL_HEIGHT, height,
146 | EGL14.EGL_NONE
147 | };
148 | mEGLSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, configs[0], surfaceAttribs, 0);
149 | checkEglError("eglCreatePbufferSurface");
150 | if (mEGLSurface == null) {
151 | throw new RuntimeException("surface was null");
152 | }
153 | }
154 | /**
155 | * Discard all resources held by this class, notably the EGL context.
156 | */
157 | public void release() {
158 | if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
159 | EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);
160 | EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
161 | EGL14.eglReleaseThread();
162 | EGL14.eglTerminate(mEGLDisplay);
163 | }
164 | mSurface.release();
165 | // this causes a bunch of warnings that appear harmless but might confuse someone:
166 | // W BufferQueue: [unnamed-3997-2] cancelBuffer: BufferQueue has been abandoned!
167 | //mSurfaceTexture.release();
168 | mEGLDisplay = EGL14.EGL_NO_DISPLAY;
169 | mEGLContext = EGL14.EGL_NO_CONTEXT;
170 | mEGLSurface = EGL14.EGL_NO_SURFACE;
171 | mTextureRender = null;
172 | mSurface = null;
173 | mSurfaceTexture = null;
174 | }
175 | /**
176 | * Makes our EGL context and surface current.
177 | */
178 | public void makeCurrent() {
179 | if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
180 | throw new RuntimeException("eglMakeCurrent failed");
181 | }
182 | }
183 | /**
184 | * Returns the Surface that we draw onto.
185 | */
186 | public Surface getSurface() {
187 | return mSurface;
188 | }
189 | /**
190 | * Replaces the fragment shader.
191 | */
192 | public void changeFragmentShader(String fragmentShader) {
193 | mTextureRender.changeFragmentShader(fragmentShader);
194 | }
195 | /**
196 | * Latches the next buffer into the texture. Must be called from the thread that created
197 | * the OutputSurface object, after the onFrameAvailable callback has signaled that new
198 | * data is available.
199 | */
200 | public void awaitNewImage() {
201 | final int TIMEOUT_MS = 10000;
202 | synchronized (mFrameSyncObject) {
203 | while (!mFrameAvailable) {
204 | try {
205 | // Wait for onFrameAvailable() to signal us. Use a timeout to avoid
206 | // stalling the test if it doesn't arrive.
207 | mFrameSyncObject.wait(TIMEOUT_MS);
208 | if (!mFrameAvailable) {
209 | // TODO: if "spurious wakeup", continue while loop
210 | throw new RuntimeException("Surface frame wait timed out");
211 | }
212 | } catch (InterruptedException ie) {
213 | // shouldn't happen
214 | throw new RuntimeException(ie);
215 | }
216 | }
217 | mFrameAvailable = false;
218 | }
219 | // Latch the data.
220 | mTextureRender.checkGlError("before updateTexImage");
221 | mSurfaceTexture.updateTexImage();
222 | }
223 | /**
224 | * Wait up to given timeout until new image become available.
225 | * @param timeoutMs
226 | * @return true if new image is available. false for no new image until timeout.
227 | */
228 | public boolean checkForNewImage(int timeoutMs) {
229 | synchronized (mFrameSyncObject) {
230 | while (!mFrameAvailable) {
231 | try {
232 | // Wait for onFrameAvailable() to signal us. Use a timeout to avoid
233 | // stalling the test if it doesn't arrive.
234 | mFrameSyncObject.wait(timeoutMs);
235 | if (!mFrameAvailable) {
236 | return false;
237 | }
238 | } catch (InterruptedException ie) {
239 | // shouldn't happen
240 | throw new RuntimeException(ie);
241 | }
242 | }
243 | mFrameAvailable = false;
244 | }
245 | // Latch the data.
246 | mTextureRender.checkGlError("before updateTexImage");
247 | mSurfaceTexture.updateTexImage();
248 | return true;
249 | }
250 | /**
251 | * Draws the data from SurfaceTexture onto the current EGL surface.
252 | */
253 | public void drawImage() {
254 | mTextureRender.drawFrame(mSurfaceTexture);
255 | }
256 | @Override
257 | public void onFrameAvailable(SurfaceTexture st) {
258 | if (VERBOSE) Log.d(TAG, "new frame available");
259 | synchronized (mFrameSyncObject) {
260 | if (mFrameAvailable) {
261 | throw new RuntimeException("mFrameAvailable already set, frame could be dropped");
262 | }
263 | mFrameAvailable = true;
264 | mFrameSyncObject.notifyAll();
265 | }
266 | }
267 | /**
268 | * Checks for EGL errors.
269 | */
270 | private void checkEglError(String msg) {
271 | int error;
272 | if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) {
273 | throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error));
274 | }
275 | }
276 | }
277 |
--------------------------------------------------------------------------------
/lib/src/main/java/net/ypresto/androidtranscoder/engine/PassThroughTrackTranscoder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Yuya Tanaka
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package net.ypresto.androidtranscoder.engine;
17 |
18 | import android.annotation.SuppressLint;
19 | import android.media.MediaCodec;
20 | import android.media.MediaExtractor;
21 | import android.media.MediaFormat;
22 |
23 | import java.nio.ByteBuffer;
24 | import java.nio.ByteOrder;
25 |
26 | public class PassThroughTrackTranscoder implements TrackTranscoder {
27 | private final MediaExtractor mExtractor;
28 | private final int mTrackIndex;
29 | private final QueuedMuxer mMuxer;
30 | private final QueuedMuxer.SampleType mSampleType;
31 | private final MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();
32 | private int mBufferSize;
33 | private ByteBuffer mBuffer;
34 | private boolean mIsEOS;
35 | private MediaFormat mActualOutputFormat;
36 | private long mWrittenPresentationTimeUs;
37 |
38 | public PassThroughTrackTranscoder(MediaExtractor extractor, int trackIndex,
39 | QueuedMuxer muxer, QueuedMuxer.SampleType sampleType) {
40 | mExtractor = extractor;
41 | mTrackIndex = trackIndex;
42 | mMuxer = muxer;
43 | mSampleType = sampleType;
44 |
45 | mActualOutputFormat = mExtractor.getTrackFormat(mTrackIndex);
46 | mMuxer.setOutputFormat(mSampleType, mActualOutputFormat);
47 | mBufferSize = mActualOutputFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
48 | mBuffer = ByteBuffer.allocateDirect(mBufferSize).order(ByteOrder.nativeOrder());
49 | }
50 |
51 | @Override
52 | public void setup() {
53 | }
54 |
55 | @Override
56 | public MediaFormat getDeterminedFormat() {
57 | return mActualOutputFormat;
58 | }
59 |
60 | @SuppressLint("Assert")
61 | @Override
62 | public boolean stepPipeline() {
63 | if (mIsEOS) return false;
64 | int trackIndex = mExtractor.getSampleTrackIndex();
65 | if (trackIndex < 0) {
66 | mBuffer.clear();
67 | mBufferInfo.set(0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
68 | mMuxer.writeSampleData(mSampleType, mBuffer, mBufferInfo);
69 | mIsEOS = true;
70 | return true;
71 | }
72 | if (trackIndex != mTrackIndex) return false;
73 |
74 | mBuffer.clear();
75 | int sampleSize = mExtractor.readSampleData(mBuffer, 0);
76 | assert sampleSize <= mBufferSize;
77 | boolean isKeyFrame = (mExtractor.getSampleFlags() & MediaExtractor.SAMPLE_FLAG_SYNC) != 0;
78 | int flags = isKeyFrame ? MediaCodec.BUFFER_FLAG_SYNC_FRAME : 0;
79 | mBufferInfo.set(0, sampleSize, mExtractor.getSampleTime(), flags);
80 | mMuxer.writeSampleData(mSampleType, mBuffer, mBufferInfo);
81 | mWrittenPresentationTimeUs = mBufferInfo.presentationTimeUs;
82 |
83 | mExtractor.advance();
84 | return true;
85 | }
86 |
87 | @Override
88 | public long getWrittenPresentationTimeUs() {
89 | return mWrittenPresentationTimeUs;
90 | }
91 |
92 | @Override
93 | public boolean isFinished() {
94 | return mIsEOS;
95 | }
96 |
97 | @Override
98 | public void release() {
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/lib/src/main/java/net/ypresto/androidtranscoder/engine/QueuedMuxer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Yuya Tanaka
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package net.ypresto.androidtranscoder.engine;
17 |
18 | import android.media.MediaCodec;
19 | import android.media.MediaFormat;
20 | import android.media.MediaMuxer;
21 | import android.util.Log;
22 |
23 | import java.nio.ByteBuffer;
24 | import java.nio.ByteOrder;
25 | import java.util.ArrayList;
26 | import java.util.List;
27 |
28 | /**
29 | * This class queues until all output track formats are determined.
30 | */
31 | public class QueuedMuxer {
32 | private static final String TAG = "QueuedMuxer";
33 | private static final int BUFFER_SIZE = 64 * 1024; // I have no idea whether this value is appropriate or not...
34 | private final MediaMuxer mMuxer;
35 | private final Listener mListener;
36 | private MediaFormat mVideoFormat;
37 | private MediaFormat mAudioFormat;
38 | private int mVideoTrackIndex;
39 | private int mAudioTrackIndex;
40 | private ByteBuffer mByteBuffer;
41 | private final List
214 | * Useful for debugging.
215 | */
216 | public static void saveFrame(String filename, int width, int height) {
217 | throw new UnsupportedOperationException("Not implemented.");
218 | }
219 | }
220 |
--------------------------------------------------------------------------------
/lib/src/main/java/net/ypresto/androidtranscoder/engine/TrackTranscoder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Yuya Tanaka
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package net.ypresto.androidtranscoder.engine;
17 |
18 | import android.media.MediaFormat;
19 |
20 | public interface TrackTranscoder {
21 |
22 | void setup();
23 |
24 | /**
25 | * Get actual MediaFormat which is used to write to muxer.
26 | * To determine you should call {@link #stepPipeline()} several times.
27 | *
28 | * @return Actual output format determined by coder, or {@code null} if not yet determined.
29 | */
30 | MediaFormat getDeterminedFormat();
31 |
32 | /**
33 | * Step pipeline if output is available in any step of it.
34 | * It assumes muxer has been started, so you should call muxer.start() first.
35 | *
36 | * @return true if data moved in pipeline.
37 | */
38 | boolean stepPipeline();
39 |
40 | /**
41 | * Get presentation time of last sample written to muxer.
42 | *
43 | * @return Presentation time in micro-second. Return value is undefined if finished writing.
44 | */
45 | long getWrittenPresentationTimeUs();
46 |
47 | boolean isFinished();
48 |
49 | void release();
50 | }
51 |
--------------------------------------------------------------------------------
/lib/src/main/java/net/ypresto/androidtranscoder/engine/VideoTrackTranscoder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Yuya Tanaka
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package net.ypresto.androidtranscoder.engine;
17 |
18 | import android.media.MediaCodec;
19 | import android.media.MediaExtractor;
20 | import android.media.MediaFormat;
21 |
22 | import net.ypresto.androidtranscoder.format.MediaFormatExtraConstants;
23 |
24 | import java.io.IOException;
25 | import java.nio.ByteBuffer;
26 |
27 | // Refer: https://android.googlesource.com/platform/cts/+/lollipop-release/tests/tests/media/src/android/media/cts/ExtractDecodeEditEncodeMuxTest.java
28 | public class VideoTrackTranscoder implements TrackTranscoder {
29 | private static final String TAG = "VideoTrackTranscoder";
30 | private static final int DRAIN_STATE_NONE = 0;
31 | private static final int DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY = 1;
32 | private static final int DRAIN_STATE_CONSUMED = 2;
33 |
34 | private final MediaExtractor mExtractor;
35 | private final int mTrackIndex;
36 | private final MediaFormat mOutputFormat;
37 | private final QueuedMuxer mMuxer;
38 | private final MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();
39 | private MediaCodec mDecoder;
40 | private MediaCodec mEncoder;
41 | private ByteBuffer[] mDecoderInputBuffers;
42 | private ByteBuffer[] mEncoderOutputBuffers;
43 | private MediaFormat mActualOutputFormat;
44 | private OutputSurface mDecoderOutputSurfaceWrapper;
45 | private InputSurface mEncoderInputSurfaceWrapper;
46 | private boolean mIsExtractorEOS;
47 | private boolean mIsDecoderEOS;
48 | private boolean mIsEncoderEOS;
49 | private boolean mDecoderStarted;
50 | private boolean mEncoderStarted;
51 | private long mWrittenPresentationTimeUs;
52 |
53 | public VideoTrackTranscoder(MediaExtractor extractor, int trackIndex,
54 | MediaFormat outputFormat, QueuedMuxer muxer) {
55 | mExtractor = extractor;
56 | mTrackIndex = trackIndex;
57 | mOutputFormat = outputFormat;
58 | mMuxer = muxer;
59 | }
60 |
61 | @Override
62 | public void setup() {
63 | mExtractor.selectTrack(mTrackIndex);
64 | try {
65 | mEncoder = MediaCodec.createEncoderByType(mOutputFormat.getString(MediaFormat.KEY_MIME));
66 | } catch (IOException e) {
67 | throw new IllegalStateException(e);
68 | }
69 | mEncoder.configure(mOutputFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
70 | mEncoderInputSurfaceWrapper = new InputSurface(mEncoder.createInputSurface());
71 | mEncoderInputSurfaceWrapper.makeCurrent();
72 | mEncoder.start();
73 | mEncoderStarted = true;
74 | mEncoderOutputBuffers = mEncoder.getOutputBuffers();
75 |
76 | MediaFormat inputFormat = mExtractor.getTrackFormat(mTrackIndex);
77 | if (inputFormat.containsKey(MediaFormatExtraConstants.KEY_ROTATION_DEGREES)) {
78 | // Decoded video is rotated automatically in Android 5.0 lollipop.
79 | // Turn off here because we don't want to encode rotated one.
80 | // refer: https://android.googlesource.com/platform/frameworks/av/+blame/lollipop-release/media/libstagefright/Utils.cpp
81 | inputFormat.setInteger(MediaFormatExtraConstants.KEY_ROTATION_DEGREES, 0);
82 | }
83 | mDecoderOutputSurfaceWrapper = new OutputSurface();
84 | try {
85 | mDecoder = MediaCodec.createDecoderByType(inputFormat.getString(MediaFormat.KEY_MIME));
86 | } catch (IOException e) {
87 | throw new IllegalStateException(e);
88 | }
89 | mDecoder.configure(inputFormat, mDecoderOutputSurfaceWrapper.getSurface(), null, 0);
90 | mDecoder.start();
91 | mDecoderStarted = true;
92 | mDecoderInputBuffers = mDecoder.getInputBuffers();
93 | }
94 |
95 | @Override
96 | public MediaFormat getDeterminedFormat() {
97 | return mActualOutputFormat;
98 | }
99 |
100 | @Override
101 | public boolean stepPipeline() {
102 | boolean busy = false;
103 |
104 | int status;
105 | while (drainEncoder(0) != DRAIN_STATE_NONE) busy = true;
106 | do {
107 | status = drainDecoder(0);
108 | if (status != DRAIN_STATE_NONE) busy = true;
109 | // NOTE: not repeating to keep from deadlock when encoder is full.
110 | } while (status == DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY);
111 | while (drainExtractor(0) != DRAIN_STATE_NONE) busy = true;
112 |
113 | return busy;
114 | }
115 |
116 | @Override
117 | public long getWrittenPresentationTimeUs() {
118 | return mWrittenPresentationTimeUs;
119 | }
120 |
121 | @Override
122 | public boolean isFinished() {
123 | return mIsEncoderEOS;
124 | }
125 |
126 | // TODO: CloseGuard
127 | @Override
128 | public void release() {
129 | if (mDecoderOutputSurfaceWrapper != null) {
130 | mDecoderOutputSurfaceWrapper.release();
131 | mDecoderOutputSurfaceWrapper = null;
132 | }
133 | if (mEncoderInputSurfaceWrapper != null) {
134 | mEncoderInputSurfaceWrapper.release();
135 | mEncoderInputSurfaceWrapper = null;
136 | }
137 | if (mDecoder != null) {
138 | if (mDecoderStarted) mDecoder.stop();
139 | mDecoder.release();
140 | mDecoder = null;
141 | }
142 | if (mEncoder != null) {
143 | if (mEncoderStarted) mEncoder.stop();
144 | mEncoder.release();
145 | mEncoder = null;
146 | }
147 | }
148 |
149 | private int drainExtractor(long timeoutUs) {
150 | if (mIsExtractorEOS) return DRAIN_STATE_NONE;
151 | int trackIndex = mExtractor.getSampleTrackIndex();
152 | if (trackIndex >= 0 && trackIndex != mTrackIndex) {
153 | return DRAIN_STATE_NONE;
154 | }
155 | int result = mDecoder.dequeueInputBuffer(timeoutUs);
156 | if (result < 0) return DRAIN_STATE_NONE;
157 | if (trackIndex < 0) {
158 | mIsExtractorEOS = true;
159 | mDecoder.queueInputBuffer(result, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
160 | return DRAIN_STATE_NONE;
161 | }
162 | int sampleSize = mExtractor.readSampleData(mDecoderInputBuffers[result], 0);
163 | boolean isKeyFrame = (mExtractor.getSampleFlags() & MediaExtractor.SAMPLE_FLAG_SYNC) != 0;
164 | mDecoder.queueInputBuffer(result, 0, sampleSize, mExtractor.getSampleTime(), isKeyFrame ? MediaCodec.BUFFER_FLAG_SYNC_FRAME : 0);
165 | mExtractor.advance();
166 | return DRAIN_STATE_CONSUMED;
167 | }
168 |
169 | private int drainDecoder(long timeoutUs) {
170 | if (mIsDecoderEOS) return DRAIN_STATE_NONE;
171 | int result = mDecoder.dequeueOutputBuffer(mBufferInfo, timeoutUs);
172 | switch (result) {
173 | case MediaCodec.INFO_TRY_AGAIN_LATER:
174 | return DRAIN_STATE_NONE;
175 | case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
176 | case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
177 | return DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY;
178 | }
179 | if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
180 | mEncoder.signalEndOfInputStream();
181 | mIsDecoderEOS = true;
182 | mBufferInfo.size = 0;
183 | }
184 | boolean doRender = (mBufferInfo.size > 0);
185 | // NOTE: doRender will block if buffer (of encoder) is full.
186 | // Refer: http://bigflake.com/mediacodec/CameraToMpegTest.java.txt
187 | mDecoder.releaseOutputBuffer(result, doRender);
188 | if (doRender) {
189 | mDecoderOutputSurfaceWrapper.awaitNewImage();
190 | mDecoderOutputSurfaceWrapper.drawImage();
191 | mEncoderInputSurfaceWrapper.setPresentationTime(mBufferInfo.presentationTimeUs * 1000);
192 | mEncoderInputSurfaceWrapper.swapBuffers();
193 | }
194 | return DRAIN_STATE_CONSUMED;
195 | }
196 |
197 | private int drainEncoder(long timeoutUs) {
198 | if (mIsEncoderEOS) return DRAIN_STATE_NONE;
199 | int result = mEncoder.dequeueOutputBuffer(mBufferInfo, timeoutUs);
200 | switch (result) {
201 | case MediaCodec.INFO_TRY_AGAIN_LATER:
202 | return DRAIN_STATE_NONE;
203 | case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
204 | if (mActualOutputFormat != null)
205 | throw new RuntimeException("Video output format changed twice.");
206 | mActualOutputFormat = mEncoder.getOutputFormat();
207 | mMuxer.setOutputFormat(QueuedMuxer.SampleType.VIDEO, mActualOutputFormat);
208 | return DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY;
209 | case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
210 | mEncoderOutputBuffers = mEncoder.getOutputBuffers();
211 | return DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY;
212 | }
213 | if (mActualOutputFormat == null) {
214 | throw new RuntimeException("Could not determine actual output format.");
215 | }
216 |
217 | if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
218 | mIsEncoderEOS = true;
219 | mBufferInfo.set(0, 0, 0, mBufferInfo.flags);
220 | }
221 | if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
222 | // SPS or PPS, which should be passed by MediaFormat.
223 | mEncoder.releaseOutputBuffer(result, false);
224 | return DRAIN_STATE_SHOULD_RETRY_IMMEDIATELY;
225 | }
226 | mMuxer.writeSampleData(QueuedMuxer.SampleType.VIDEO, mEncoderOutputBuffers[result], mBufferInfo);
227 | mWrittenPresentationTimeUs = mBufferInfo.presentationTimeUs;
228 | mEncoder.releaseOutputBuffer(result, false);
229 | return DRAIN_STATE_CONSUMED;
230 | }
231 | }
232 |
--------------------------------------------------------------------------------
/lib/src/main/java/net/ypresto/androidtranscoder/format/Android16By9FormatStrategy.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Yuya Tanaka
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package net.ypresto.androidtranscoder.format;
17 |
18 | import android.media.MediaCodecInfo;
19 | import android.media.MediaFormat;
20 | import android.util.Log;
21 |
22 | class Android16By9FormatStrategy implements MediaFormatStrategy {
23 | public static final int AUDIO_BITRATE_AS_IS = -1;
24 | public static final int AUDIO_CHANNELS_AS_IS = -1;
25 | public static final int SCALE_720P = 5;
26 | private static final String TAG = "Android16By9FormatStrategy";
27 | private final int mScale;
28 | private final int mVideoBitrate;
29 | private final int mAudioBitrate;
30 | private final int mAudioChannels;
31 |
32 | public Android16By9FormatStrategy(int scale, int videoBitrate) {
33 | this(scale, videoBitrate, AUDIO_BITRATE_AS_IS, AUDIO_CHANNELS_AS_IS);
34 | }
35 |
36 | public Android16By9FormatStrategy(int scale, int videoBitrate, int audioBitrate, int audioChannels) {
37 | mScale = scale;
38 | mVideoBitrate = videoBitrate;
39 | mAudioBitrate = audioBitrate;
40 | mAudioChannels = audioChannels;
41 | }
42 |
43 | @Override
44 | public MediaFormat createVideoOutputFormat(MediaFormat inputFormat) {
45 | int width = inputFormat.getInteger(MediaFormat.KEY_WIDTH);
46 | int height = inputFormat.getInteger(MediaFormat.KEY_HEIGHT);
47 | int targetLonger = mScale * 16 * 16;
48 | int targetShorter = mScale * 16 * 9;
49 | int longer, shorter, outWidth, outHeight;
50 | if (width >= height) {
51 | longer = width;
52 | shorter = height;
53 | outWidth = targetLonger;
54 | outHeight = targetShorter;
55 | } else {
56 | shorter = width;
57 | longer = height;
58 | outWidth = targetShorter;
59 | outHeight = targetLonger;
60 | }
61 | if (longer * 9 != shorter * 16) {
62 | throw new OutputFormatUnavailableException("This video is not 16:9, and is not able to transcode. (" + width + "x" + height + ")");
63 | }
64 | if (shorter <= targetShorter) {
65 | Log.d(TAG, "This video's height is less or equal to " + targetShorter + ", pass-through. (" + width + "x" + height + ")");
66 | return null;
67 | }
68 | MediaFormat format = MediaFormat.createVideoFormat("video/avc", outWidth, outHeight);
69 | // From Nexus 4 Camera in 720p
70 | format.setInteger(MediaFormat.KEY_BIT_RATE, mVideoBitrate);
71 | format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
72 | format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 3);
73 | format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
74 | return format;
75 | }
76 |
77 | @Override
78 | public MediaFormat createAudioOutputFormat(MediaFormat inputFormat) {
79 | if (mAudioBitrate == AUDIO_BITRATE_AS_IS || mAudioChannels == AUDIO_CHANNELS_AS_IS) return null;
80 |
81 | // Use original sample rate, as resampling is not supported yet.
82 | final MediaFormat format = MediaFormat.createAudioFormat(MediaFormatExtraConstants.MIMETYPE_AUDIO_AAC,
83 | inputFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE), mAudioChannels);
84 | format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
85 | format.setInteger(MediaFormat.KEY_BIT_RATE, mAudioBitrate);
86 | return format;
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/lib/src/main/java/net/ypresto/androidtranscoder/format/Android720pFormatStrategy.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Yuya Tanaka
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package net.ypresto.androidtranscoder.format;
17 |
18 | import android.media.MediaCodecInfo;
19 | import android.media.MediaFormat;
20 | import android.util.Log;
21 |
22 | class Android720pFormatStrategy implements MediaFormatStrategy {
23 | public static final int AUDIO_BITRATE_AS_IS = -1;
24 | public static final int AUDIO_CHANNELS_AS_IS = -1;
25 | private static final String TAG = "720pFormatStrategy";
26 | private static final int LONGER_LENGTH = 1280;
27 | private static final int SHORTER_LENGTH = 720;
28 | private static final int DEFAULT_VIDEO_BITRATE = 8000 * 1000; // From Nexus 4 Camera in 720p
29 | private final int mVideoBitrate;
30 | private final int mAudioBitrate;
31 | private final int mAudioChannels;
32 |
33 | public Android720pFormatStrategy() {
34 | this(DEFAULT_VIDEO_BITRATE);
35 | }
36 |
37 | public Android720pFormatStrategy(int videoBitrate) {
38 | this(videoBitrate, AUDIO_BITRATE_AS_IS, AUDIO_CHANNELS_AS_IS);
39 | }
40 |
41 | public Android720pFormatStrategy(int videoBitrate, int audioBitrate, int audioChannels) {
42 | mVideoBitrate = videoBitrate;
43 | mAudioBitrate = audioBitrate;
44 | mAudioChannels = audioChannels;
45 | }
46 |
47 | @Override
48 | public MediaFormat createVideoOutputFormat(MediaFormat inputFormat) {
49 | int width = inputFormat.getInteger(MediaFormat.KEY_WIDTH);
50 | int height = inputFormat.getInteger(MediaFormat.KEY_HEIGHT);
51 | int longer, shorter, outWidth, outHeight;
52 | if (width >= height) {
53 | longer = width;
54 | shorter = height;
55 | outWidth = LONGER_LENGTH;
56 | outHeight = SHORTER_LENGTH;
57 | } else {
58 | shorter = width;
59 | longer = height;
60 | outWidth = SHORTER_LENGTH;
61 | outHeight = LONGER_LENGTH;
62 | }
63 | if (longer * 9 != shorter * 16) {
64 | throw new OutputFormatUnavailableException("This video is not 16:9, and is not able to transcode. (" + width + "x" + height + ")");
65 | }
66 | if (shorter <= SHORTER_LENGTH) {
67 | Log.d(TAG, "This video is less or equal to 720p, pass-through. (" + width + "x" + height + ")");
68 | return null;
69 | }
70 | MediaFormat format = MediaFormat.createVideoFormat("video/avc", outWidth, outHeight);
71 | // From Nexus 4 Camera in 720p
72 | format.setInteger(MediaFormat.KEY_BIT_RATE, mVideoBitrate);
73 | format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
74 | format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 3);
75 | format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
76 | return format;
77 | }
78 |
79 | @Override
80 | public MediaFormat createAudioOutputFormat(MediaFormat inputFormat) {
81 | if (mAudioBitrate == AUDIO_BITRATE_AS_IS || mAudioChannels == AUDIO_CHANNELS_AS_IS) return null;
82 |
83 | // Use original sample rate, as resampling is not supported yet.
84 | final MediaFormat format = MediaFormat.createAudioFormat(MediaFormatExtraConstants.MIMETYPE_AUDIO_AAC,
85 | inputFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE), mAudioChannels);
86 | format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
87 | format.setInteger(MediaFormat.KEY_BIT_RATE, mAudioBitrate);
88 | return format;
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/lib/src/main/java/net/ypresto/androidtranscoder/format/ExportPreset960x540Strategy.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Yuya Tanaka
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package net.ypresto.androidtranscoder.format;
17 |
18 | import android.media.MediaFormat;
19 | import android.util.Log;
20 |
21 | /**
22 | * Created by yuya.tanaka on 2014/11/20.
23 | */
24 | class ExportPreset960x540Strategy implements MediaFormatStrategy {
25 | private static final String TAG = "ExportPreset960x540Strategy";
26 |
27 | @Override
28 | public MediaFormat createVideoOutputFormat(MediaFormat inputFormat) {
29 | // TODO: detect non-baseline profile and throw exception
30 | int width = inputFormat.getInteger(MediaFormat.KEY_WIDTH);
31 | int height = inputFormat.getInteger(MediaFormat.KEY_HEIGHT);
32 | MediaFormat outputFormat = MediaFormatPresets.getExportPreset960x540(width, height);
33 | int outWidth = outputFormat.getInteger(MediaFormat.KEY_WIDTH);
34 | int outHeight = outputFormat.getInteger(MediaFormat.KEY_HEIGHT);
35 | Log.d(TAG, String.format("inputFormat: %dx%d => outputFormat: %dx%d", width, height, outWidth, outHeight));
36 | return outputFormat;
37 | }
38 |
39 | @Override
40 | public MediaFormat createAudioOutputFormat(MediaFormat inputFormat) {
41 | // TODO
42 | return null;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/lib/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatExtraConstants.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Yuya Tanaka
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package net.ypresto.androidtranscoder.format;
17 |
18 | public class MediaFormatExtraConstants {
19 | // from MediaFormat of API level >= 21, but might be usable in older APIs as native code implementation exists.
20 | // https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/ACodec.cpp#2621
21 | // NOTE: native code enforces baseline profile.
22 | // https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/ACodec.cpp#2638
23 | /** For encoder parameter. Use value of MediaCodecInfo.CodecProfileLevel.AVCProfile* . */
24 | public static final String KEY_PROFILE = "profile";
25 |
26 | // from https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/ACodec.cpp#2623
27 | /** For encoder parameter. Use value of MediaCodecInfo.CodecProfileLevel.AVCLevel* . */
28 | public static final String KEY_LEVEL = "level";
29 |
30 | // from https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/MediaCodec.cpp#2197
31 | /** Included in MediaFormat from {@link android.media.MediaExtractor#getTrackFormat(int)}. Value is {@link java.nio.ByteBuffer}. */
32 | public static final String KEY_AVC_SPS = "csd-0";
33 | /** Included in MediaFormat from {@link android.media.MediaExtractor#getTrackFormat(int)}. Value is {@link java.nio.ByteBuffer}. */
34 | public static final String KEY_AVC_PPS = "csd-1";
35 |
36 | /**
37 | * For decoder parameter and included in MediaFormat from {@link android.media.MediaExtractor#getTrackFormat(int)}.
38 | * Decoder rotates specified degrees before rendering video to surface.
39 | * NOTE: Only included in track format of API >= 21.
40 | */
41 | public static final String KEY_ROTATION_DEGREES = "rotation-degrees";
42 |
43 | // Video formats
44 | // from MediaFormat of API level >= 21
45 | public static final String MIMETYPE_VIDEO_AVC = "video/avc";
46 | public static final String MIMETYPE_VIDEO_H263 = "video/3gpp";
47 | public static final String MIMETYPE_VIDEO_VP8 = "video/x-vnd.on2.vp8";
48 |
49 | // Audio formats
50 | // from MediaFormat of API level >= 21
51 | public static final String MIMETYPE_AUDIO_AAC = "audio/mp4a-latm";
52 |
53 | private MediaFormatExtraConstants() {
54 | throw new RuntimeException();
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/lib/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatPresets.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Yuya Tanaka
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package net.ypresto.androidtranscoder.format;
17 |
18 | import android.media.MediaCodecInfo;
19 | import android.media.MediaFormat;
20 |
21 | // Refer for example: https://gist.github.com/wobbals/3990442
22 | // Refer for preferred parameters: https://developer.apple.com/library/ios/documentation/networkinginternet/conceptual/streamingmediaguide/UsingHTTPLiveStreaming/UsingHTTPLiveStreaming.html#//apple_ref/doc/uid/TP40008332-CH102-SW8
23 | // Refer for available keys: (ANDROID ROOT)/media/libstagefright/ACodec.cpp
24 | public class MediaFormatPresets {
25 | private static final int LONGER_LENGTH_960x540 = 960;
26 |
27 | private MediaFormatPresets() {
28 | }
29 |
30 | // preset similar to iOS SDK's AVAssetExportPreset960x540
31 | @Deprecated
32 | public static MediaFormat getExportPreset960x540() {
33 | MediaFormat format = MediaFormat.createVideoFormat("video/avc", 960, 540);
34 | format.setInteger(MediaFormat.KEY_BIT_RATE, 5500 * 1000);
35 | format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
36 | format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
37 | format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
38 | return format;
39 | }
40 |
41 | /**
42 | * Preset similar to iOS SDK's AVAssetExportPreset960x540.
43 | * Note that encoding resolutions of this preset are not supported in all devices e.g. Nexus 4.
44 | * On unsupported device encoded video stream will be broken without any exception.
45 | * @param originalWidth Input video width.
46 | * @param originalHeight Input video height.
47 | * @return MediaFormat instance, or null if pass through.
48 | */
49 | public static MediaFormat getExportPreset960x540(int originalWidth, int originalHeight) {
50 | int longerLength = Math.max(originalWidth, originalHeight);
51 | int shorterLength = Math.min(originalWidth, originalHeight);
52 |
53 | if (longerLength <= LONGER_LENGTH_960x540) return null; // don't upscale
54 |
55 | int residue = LONGER_LENGTH_960x540 * shorterLength % longerLength;
56 | if (residue != 0) {
57 | double ambiguousShorter = (double) LONGER_LENGTH_960x540 * shorterLength / longerLength;
58 | throw new OutputFormatUnavailableException(String.format(
59 | "Could not fit to integer, original: (%d, %d), scaled: (%d, %f)",
60 | longerLength, shorterLength, LONGER_LENGTH_960x540, ambiguousShorter));
61 | }
62 |
63 | int scaledShorter = LONGER_LENGTH_960x540 * shorterLength / longerLength;
64 | int width, height;
65 | if (originalWidth >= originalHeight) {
66 | width = LONGER_LENGTH_960x540;
67 | height = scaledShorter;
68 | } else {
69 | width = scaledShorter;
70 | height = LONGER_LENGTH_960x540;
71 | }
72 |
73 | MediaFormat format = MediaFormat.createVideoFormat("video/avc", width, height);
74 | format.setInteger(MediaFormat.KEY_BIT_RATE, 5500 * 1000);
75 | format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
76 | format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
77 | format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
78 | return format;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/lib/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatStrategy.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Yuya Tanaka
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package net.ypresto.androidtranscoder.format;
17 |
18 | import android.media.MediaFormat;
19 |
20 | public interface MediaFormatStrategy {
21 |
22 | /**
23 | * Returns preferred video format for encoding.
24 | *
25 | * @param inputFormat MediaFormat from MediaExtractor, contains csd-0/csd-1.
26 | * @return null for passthrough.
27 | * @throws OutputFormatUnavailableException if input could not be transcoded because of restrictions.
28 | */
29 | public MediaFormat createVideoOutputFormat(MediaFormat inputFormat);
30 |
31 | /**
32 | * Caution: this method should return null currently.
33 | *
34 | * @return null for passthrough.
35 | * @throws OutputFormatUnavailableException if input could not be transcoded because of restrictions.
36 | */
37 | public MediaFormat createAudioOutputFormat(MediaFormat inputFormat);
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/lib/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatStrategyPresets.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Yuya Tanaka
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package net.ypresto.androidtranscoder.format;
17 |
18 | public class MediaFormatStrategyPresets {
19 | public static final int AUDIO_BITRATE_AS_IS = -1;
20 | public static final int AUDIO_CHANNELS_AS_IS = -1;
21 |
22 | /**
23 | * @deprecated Use {@link #createExportPreset960x540Strategy()}.
24 | */
25 | @Deprecated
26 | public static final MediaFormatStrategy EXPORT_PRESET_960x540 = new ExportPreset960x540Strategy();
27 |
28 | /**
29 | * Preset based on Nexus 4 camera recording with 720p quality.
30 | * This preset is ensured to work on any Android >=4.3 devices by Android CTS (if codec is available).
31 | * Default bitrate is 8Mbps. {@link #createAndroid720pStrategy(int)} to specify bitrate.
32 | */
33 | public static MediaFormatStrategy createAndroid720pStrategy() {
34 | return new Android720pFormatStrategy();
35 | }
36 |
37 | /**
38 | * Preset based on Nexus 4 camera recording with 720p quality.
39 | * This preset is ensured to work on any Android >=4.3 devices by Android CTS (if codec is available).
40 | * Audio track will be copied as-is.
41 | *
42 | * @param bitrate Preferred bitrate for video encoding.
43 | */
44 | public static MediaFormatStrategy createAndroid720pStrategy(int bitrate) {
45 | return new Android720pFormatStrategy(bitrate);
46 | }
47 |
48 | /**
49 | * Preset based on Nexus 4 camera recording with 720p quality.
50 | * This preset is ensured to work on any Android >=4.3 devices by Android CTS (if codec is available).
51 | *
52 | * Note: audio transcoding is experimental feature.
53 | *
54 | * @param bitrate Preferred bitrate for video encoding.
55 | * @param audioBitrate Preferred bitrate for audio encoding.
56 | * @param audioChannels Output audio channels.
57 | */
58 | public static MediaFormatStrategy createAndroid720pStrategy(int bitrate, int audioBitrate, int audioChannels) {
59 | return new Android720pFormatStrategy(bitrate, audioBitrate, audioChannels);
60 | }
61 |
62 | /**
63 | * Preset similar to iOS SDK's AVAssetExportPreset960x540.
64 | * Note that encoding resolutions of this preset are not supported in all devices e.g. Nexus 4.
65 | * On unsupported device encoded video stream will be broken without any exception.
66 | */
67 | public static MediaFormatStrategy createExportPreset960x540Strategy() {
68 | return new ExportPreset960x540Strategy();
69 | }
70 |
71 | private MediaFormatStrategyPresets() {
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/lib/src/main/java/net/ypresto/androidtranscoder/format/OutputFormatUnavailableException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Yuya Tanaka
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package net.ypresto.androidtranscoder.format;
17 |
18 | public class OutputFormatUnavailableException extends RuntimeException {
19 | public OutputFormatUnavailableException(String detailMessage) {
20 | super(detailMessage);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lib/src/main/java/net/ypresto/androidtranscoder/utils/AvcCsdUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Yuya Tanaka
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package net.ypresto.androidtranscoder.utils;
17 |
18 | import android.media.MediaFormat;
19 |
20 | import net.ypresto.androidtranscoder.format.MediaFormatExtraConstants;
21 |
22 | import java.nio.ByteBuffer;
23 | import java.util.Arrays;
24 |
25 | public class AvcCsdUtils {
26 | // Refer: https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/MediaCodec.cpp#2198
27 | // Refer: http://stackoverflow.com/a/2861340
28 | private static final byte[] AVC_START_CODE_3 = {0x00, 0x00, 0x01};
29 | private static final byte[] AVC_START_CODE_4 = {0x00, 0x00, 0x00, 0x01};
30 | // Refer: http://www.cardinalpeak.com/blog/the-h-264-sequence-parameter-set/
31 | private static final byte AVC_SPS_NAL = 103; // 0<<7 + 3<<5 + 7<<0
32 | // https://tools.ietf.org/html/rfc6184
33 | private static final byte AVC_SPS_NAL_2 = 39; // 0<<7 + 1<<5 + 7<<0
34 | private static final byte AVC_SPS_NAL_3 = 71; // 0<<7 + 2<<5 + 7<<0
35 |
36 | /**
37 | * @return ByteBuffer contains SPS without NAL header.
38 | */
39 | public static ByteBuffer getSpsBuffer(MediaFormat format) {
40 | ByteBuffer sourceBuffer = format.getByteBuffer(MediaFormatExtraConstants.KEY_AVC_SPS).asReadOnlyBuffer(); // might be direct buffer
41 | ByteBuffer prefixedSpsBuffer = ByteBuffer.allocate(sourceBuffer.limit()).order(sourceBuffer.order());
42 | prefixedSpsBuffer.put(sourceBuffer);
43 | prefixedSpsBuffer.flip();
44 |
45 | skipStartCode(prefixedSpsBuffer);
46 |
47 | byte spsNalData = prefixedSpsBuffer.get();
48 | if (spsNalData != AVC_SPS_NAL && spsNalData != AVC_SPS_NAL_2 && spsNalData != AVC_SPS_NAL_3) {
49 | throw new IllegalStateException("Got non SPS NAL data.");
50 | }
51 |
52 | return prefixedSpsBuffer.slice();
53 | }
54 |
55 | private static void skipStartCode(ByteBuffer prefixedSpsBuffer) {
56 | byte[] prefix3 = new byte[3];
57 | prefixedSpsBuffer.get(prefix3);
58 | if (Arrays.equals(prefix3, AVC_START_CODE_3)) return;
59 |
60 | byte[] prefix4 = Arrays.copyOf(prefix3, 4);
61 | prefix4[3] = prefixedSpsBuffer.get();
62 | if (Arrays.equals(prefix4, AVC_START_CODE_4)) return;
63 | throw new IllegalStateException("AVC NAL start code does not found in csd.");
64 | }
65 |
66 | private AvcCsdUtils() {
67 | throw new RuntimeException();
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/lib/src/main/java/net/ypresto/androidtranscoder/utils/AvcSpsUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Yuya Tanaka
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package net.ypresto.androidtranscoder.utils;
17 |
18 | import java.nio.ByteBuffer;
19 |
20 | public class AvcSpsUtils {
21 | public static byte getProfileIdc(ByteBuffer spsBuffer) {
22 | // Refer: http://www.cardinalpeak.com/blog/the-h-264-sequence-parameter-set/
23 | // First byte after NAL.
24 | return spsBuffer.get(0);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/lib/src/main/java/net/ypresto/androidtranscoder/utils/ISO6709LocationParser.java:
--------------------------------------------------------------------------------
1 | package net.ypresto.androidtranscoder.utils;
2 |
3 | import java.util.regex.Matcher;
4 | import java.util.regex.Pattern;
5 |
6 | public class ISO6709LocationParser {
7 | private final Pattern pattern;
8 |
9 | public ISO6709LocationParser() {
10 | this.pattern = Pattern.compile("([+\\-][0-9.]+)([+\\-][0-9.]+)");
11 | }
12 |
13 | /**
14 | * This method parses the given string representing a geographic point location by coordinates in ISO 6709 format
15 | * and returns the latitude and the longitude in float. If location
is not in ISO 6709 format,
16 | * this method returns null
17 | *
18 | * @param location a String representing a geographic point location by coordinates in ISO 6709 format
19 | * @return null
if the given string is not as expected, an array of floats with size 2,
20 | * where the first element represents latitude and the second represents longitude, otherwise.
21 | */
22 | public float[] parse(String location) {
23 | if (location == null) return null;
24 | Matcher m = pattern.matcher(location);
25 | if (m.find() && m.groupCount() == 2) {
26 | String latstr = m.group(1);
27 | String lonstr = m.group(2);
28 | try {
29 | float lat = Float.parseFloat(latstr);
30 | float lon = Float.parseFloat(lonstr);
31 | return new float[]{lat, lon};
32 | } catch (NumberFormatException ignored) {
33 | }
34 | }
35 | return null;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lib/src/main/java/net/ypresto/androidtranscoder/utils/MediaExtractorUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Yuya Tanaka
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package net.ypresto.androidtranscoder.utils;
17 |
18 | import android.media.MediaExtractor;
19 | import android.media.MediaFormat;
20 |
21 | public class MediaExtractorUtils {
22 |
23 | private MediaExtractorUtils() {
24 | }
25 |
26 | public static class TrackResult {
27 |
28 | private TrackResult() {
29 | }
30 |
31 | public int mVideoTrackIndex;
32 | public String mVideoTrackMime;
33 | public MediaFormat mVideoTrackFormat;
34 | public int mAudioTrackIndex;
35 | public String mAudioTrackMime;
36 | public MediaFormat mAudioTrackFormat;
37 | }
38 |
39 | public static TrackResult getFirstVideoAndAudioTrack(MediaExtractor extractor) {
40 | TrackResult trackResult = new TrackResult();
41 | trackResult.mVideoTrackIndex = -1;
42 | trackResult.mAudioTrackIndex = -1;
43 | int trackCount = extractor.getTrackCount();
44 | for (int i = 0; i < trackCount; i++) {
45 | MediaFormat format = extractor.getTrackFormat(i);
46 | String mime = format.getString(MediaFormat.KEY_MIME);
47 | if (trackResult.mVideoTrackIndex < 0 && mime.startsWith("video/")) {
48 | trackResult.mVideoTrackIndex = i;
49 | trackResult.mVideoTrackMime = mime;
50 | trackResult.mVideoTrackFormat = format;
51 | } else if (trackResult.mAudioTrackIndex < 0 && mime.startsWith("audio/")) {
52 | trackResult.mAudioTrackIndex = i;
53 | trackResult.mAudioTrackMime = mime;
54 | trackResult.mAudioTrackFormat = format;
55 | }
56 | if (trackResult.mVideoTrackIndex >= 0 && trackResult.mAudioTrackIndex >= 0) break;
57 | }
58 | if (trackResult.mVideoTrackIndex < 0 || trackResult.mAudioTrackIndex < 0) {
59 | throw new IllegalArgumentException("extractor does not contain video and/or audio tracks.");
60 | }
61 | return trackResult;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/script/add-header.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | for file in `find lib/src -name '*.java'`; do
4 | grep 'http://www.apache.org/licenses/LICENSE-2.0' $file > /dev/null && continue
5 | cat script/header.txt $file > ${file}.new
6 | mv ${file}.new $file
7 | done
8 |
--------------------------------------------------------------------------------
/script/header.txt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Yuya Tanaka
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':lib', ':example'
2 |
--------------------------------------------------------------------------------