├── .classpath ├── .gitignore ├── .project ├── .settings └── org.eclipse.jdt.core.prefs ├── AndroidManifest.xml ├── README.md ├── ic_launcher-web.png ├── lib_so ├── libmedia.so ├── libmedia_jni.so ├── libvideoeditor_core.so ├── libvideoeditor_jni.so ├── libvideoeditor_osal.so ├── libvideoeditor_videofilters.so └── libvideoeditorplayer.so ├── libs └── android-support-v4.jar ├── proguard-project.txt ├── project.properties ├── res ├── drawable-hdpi │ └── ic_launcher.png ├── drawable-mdpi │ └── ic_launcher.png ├── drawable-xhdpi │ └── ic_launcher.png ├── drawable-xxhdpi │ └── ic_launcher.png ├── values-v11 │ └── styles.xml ├── values-v14 │ └── styles.xml └── values │ ├── strings.xml │ └── styles.xml └── src └── android └── media └── videoeditor ├── AudioTrack.java ├── Effect.java ├── EffectColor.java ├── EffectKenBurns.java ├── ExtractAudioWaveformProgressListener.java ├── MediaArtistNativeHelper.java ├── MediaImageItem.java ├── MediaItem.java ├── MediaProperties.java ├── MediaVideoItem.java ├── Overlay.java ├── OverlayFrame.java ├── Transition.java ├── TransitionAlpha.java ├── TransitionCrossfade.java ├── TransitionFadeBlack.java ├── TransitionSliding.java ├── VideoEditor.java ├── VideoEditorFactory.java ├── VideoEditorImpl.java ├── VideoEditorProfile.java └── WaveformData.java /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | gen/ 2 | bin/ 3 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | VideoEditorAPI 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled 3 | org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate 4 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 5 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve 6 | org.eclipse.jdt.core.compiler.compliance=1.6 7 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate 8 | org.eclipse.jdt.core.compiler.debug.localVariable=generate 9 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate 10 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error 11 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error 12 | org.eclipse.jdt.core.compiler.source=1.6 13 | -------------------------------------------------------------------------------- /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | 10 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VideoEditorAPI 2 | 3 | API for [VideoEditor](https://github.com/pop1030123/VideoEditor) 4 | -------------------------------------------------------------------------------- /ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pop1030123/VideoEditorAPI/86f470839021b71a25eeeb4f7b0e6461f69561ee/ic_launcher-web.png -------------------------------------------------------------------------------- /lib_so/libmedia.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pop1030123/VideoEditorAPI/86f470839021b71a25eeeb4f7b0e6461f69561ee/lib_so/libmedia.so -------------------------------------------------------------------------------- /lib_so/libmedia_jni.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pop1030123/VideoEditorAPI/86f470839021b71a25eeeb4f7b0e6461f69561ee/lib_so/libmedia_jni.so -------------------------------------------------------------------------------- /lib_so/libvideoeditor_core.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pop1030123/VideoEditorAPI/86f470839021b71a25eeeb4f7b0e6461f69561ee/lib_so/libvideoeditor_core.so -------------------------------------------------------------------------------- /lib_so/libvideoeditor_jni.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pop1030123/VideoEditorAPI/86f470839021b71a25eeeb4f7b0e6461f69561ee/lib_so/libvideoeditor_jni.so -------------------------------------------------------------------------------- /lib_so/libvideoeditor_osal.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pop1030123/VideoEditorAPI/86f470839021b71a25eeeb4f7b0e6461f69561ee/lib_so/libvideoeditor_osal.so -------------------------------------------------------------------------------- /lib_so/libvideoeditor_videofilters.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pop1030123/VideoEditorAPI/86f470839021b71a25eeeb4f7b0e6461f69561ee/lib_so/libvideoeditor_videofilters.so -------------------------------------------------------------------------------- /lib_so/libvideoeditorplayer.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pop1030123/VideoEditorAPI/86f470839021b71a25eeeb4f7b0e6461f69561ee/lib_so/libvideoeditorplayer.so -------------------------------------------------------------------------------- /libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pop1030123/VideoEditorAPI/86f470839021b71a25eeeb4f7b0e6461f69561ee/libs/android-support-v4.jar -------------------------------------------------------------------------------- /proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-16 15 | android.library=false 16 | -------------------------------------------------------------------------------- /res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pop1030123/VideoEditorAPI/86f470839021b71a25eeeb4f7b0e6461f69561ee/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pop1030123/VideoEditorAPI/86f470839021b71a25eeeb4f7b0e6461f69561ee/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pop1030123/VideoEditorAPI/86f470839021b71a25eeeb4f7b0e6461f69561ee/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pop1030123/VideoEditorAPI/86f470839021b71a25eeeb4f7b0e6461f69561ee/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/values-v11/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /res/values-v14/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | VideoEditorAPI 4 | 5 | 6 | -------------------------------------------------------------------------------- /res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 14 | 15 | 16 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/android/media/videoeditor/AudioTrack.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 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 | 17 | 18 | package android.media.videoeditor; 19 | 20 | import java.io.File; 21 | import java.io.IOException; 22 | import java.lang.ref.SoftReference; 23 | 24 | import android.media.videoeditor.MediaArtistNativeHelper.Properties; 25 | 26 | /** 27 | * This class allows to handle an audio track. This audio file is mixed with the 28 | * audio samples of the media items. 29 | * {@hide} 30 | */ 31 | public class AudioTrack { 32 | 33 | /** 34 | * Instance variables 35 | * Private object for calling native methods via MediaArtistNativeHelper 36 | */ 37 | private final MediaArtistNativeHelper mMANativeHelper; 38 | private final String mUniqueId; 39 | private final String mFilename; 40 | private long mStartTimeMs; 41 | private long mTimelineDurationMs; 42 | private int mVolumePercent; 43 | private long mBeginBoundaryTimeMs; 44 | private long mEndBoundaryTimeMs; 45 | private boolean mLoop; 46 | private boolean mMuted; 47 | private final long mDurationMs; 48 | private final int mAudioChannels; 49 | private final int mAudioType; 50 | private final int mAudioBitrate; 51 | private final int mAudioSamplingFrequency; 52 | /** 53 | * Ducking variables 54 | */ 55 | private int mDuckingThreshold; 56 | private int mDuckedTrackVolume; 57 | private boolean mIsDuckingEnabled; 58 | 59 | /** 60 | * The audio waveform filename 61 | */ 62 | private String mAudioWaveformFilename; 63 | 64 | /** 65 | * The audio waveform data 66 | */ 67 | private SoftReference mWaveformData; 68 | 69 | /** 70 | * An object of this type cannot be instantiated by using the default 71 | * constructor 72 | */ 73 | @SuppressWarnings("unused") 74 | private AudioTrack() throws IOException { 75 | this(null, null, null); 76 | } 77 | 78 | /** 79 | * Constructor 80 | * 81 | * @param editor The video editor reference 82 | * @param audioTrackId The audio track id 83 | * @param filename The absolute file name 84 | * 85 | * @throws IOException if file is not found 86 | * @throws IllegalArgumentException if file format is not supported or if 87 | * the codec is not supported or if editor is not of type 88 | * VideoEditorImpl. 89 | */ 90 | public AudioTrack(VideoEditor editor, String audioTrackId, String filename) throws IOException { 91 | this(editor, audioTrackId, filename, 0, 0, MediaItem.END_OF_FILE, false, 100, false, false, 92 | 0, 0, null); 93 | } 94 | 95 | /** 96 | * Constructor 97 | * 98 | * @param editor The video editor reference 99 | * @param audioTrackId The audio track id 100 | * @param filename The audio filename. In case file contains Audio and Video, 101 | * only the Audio stream will be used as Audio Track. 102 | * @param startTimeMs the start time in milliseconds (relative to the 103 | * timeline) 104 | * @param beginMs start time in the audio track in milliseconds (relative to 105 | * the beginning of the audio track) 106 | * @param endMs end time in the audio track in milliseconds (relative to the 107 | * beginning of the audio track) 108 | * @param loop true to loop the audio track 109 | * @param volume The volume in percentage 110 | * @param muted true if the audio track is muted 111 | * @param threshold Ducking will be activated when the relative energy in 112 | * the media items audio signal goes above this value. The valid 113 | * range of values is 0 to 90. 114 | * @param duckedTrackVolume The relative volume of the audio track when 115 | * ducking is active. The valid range of values is 0 to 100. 116 | * @param audioWaveformFilename The name of the waveform file 117 | * 118 | * @throws IOException if file is not found 119 | * @throws IllegalArgumentException if file format is not supported or if 120 | * the codec is not supported or if editor is not of type 121 | * VideoEditorImpl. 122 | */ 123 | AudioTrack(VideoEditor editor, String audioTrackId, String filename, 124 | long startTimeMs,long beginMs, long endMs, boolean loop, 125 | int volume, boolean muted,boolean duckingEnabled, 126 | int duckThreshold, int duckedTrackVolume, 127 | String audioWaveformFilename) throws IOException { 128 | Properties properties = null; 129 | 130 | File file = new File(filename); 131 | if (!file.exists()) { 132 | throw new IOException(filename + " not found ! "); 133 | } 134 | 135 | /*Compare file_size with 2GB*/ 136 | if (VideoEditor.MAX_SUPPORTED_FILE_SIZE <= file.length()) { 137 | throw new IllegalArgumentException("File size is more than 2GB"); 138 | } 139 | 140 | if (editor instanceof VideoEditorImpl) { 141 | mMANativeHelper = ((VideoEditorImpl)editor).getNativeContext(); 142 | } else { 143 | throw new IllegalArgumentException("editor is not of type VideoEditorImpl"); 144 | } 145 | try { 146 | properties = mMANativeHelper.getMediaProperties(filename); 147 | } catch (Exception e) { 148 | throw new IllegalArgumentException(e.getMessage() + " : " + filename); 149 | } 150 | int fileType = mMANativeHelper.getFileType(properties.fileType); 151 | switch (fileType) { 152 | case MediaProperties.FILE_3GP: 153 | case MediaProperties.FILE_MP4: 154 | case MediaProperties.FILE_MP3: 155 | case MediaProperties.FILE_AMR: 156 | break; 157 | 158 | default: { 159 | throw new IllegalArgumentException("Unsupported input file type: " + fileType); 160 | } 161 | } 162 | switch (mMANativeHelper.getAudioCodecType(properties.audioFormat)) { 163 | case MediaProperties.ACODEC_AMRNB: 164 | case MediaProperties.ACODEC_AMRWB: 165 | case MediaProperties.ACODEC_AAC_LC: 166 | case MediaProperties.ACODEC_MP3: 167 | break; 168 | default: 169 | throw new IllegalArgumentException("Unsupported Audio Codec Format in Input File"); 170 | } 171 | 172 | if (endMs == MediaItem.END_OF_FILE) { 173 | endMs = properties.audioDuration; 174 | } 175 | 176 | mUniqueId = audioTrackId; 177 | mFilename = filename; 178 | mStartTimeMs = startTimeMs; 179 | mDurationMs = properties.audioDuration; 180 | mAudioChannels = properties.audioChannels; 181 | mAudioBitrate = properties.audioBitrate; 182 | mAudioSamplingFrequency = properties.audioSamplingFrequency; 183 | mAudioType = properties.audioFormat; 184 | mTimelineDurationMs = endMs - beginMs; 185 | mVolumePercent = volume; 186 | 187 | mBeginBoundaryTimeMs = beginMs; 188 | mEndBoundaryTimeMs = endMs; 189 | 190 | mLoop = loop; 191 | mMuted = muted; 192 | mIsDuckingEnabled = duckingEnabled; 193 | mDuckingThreshold = duckThreshold; 194 | mDuckedTrackVolume = duckedTrackVolume; 195 | 196 | mAudioWaveformFilename = audioWaveformFilename; 197 | if (audioWaveformFilename != null) { 198 | mWaveformData = 199 | new SoftReference(new WaveformData(audioWaveformFilename)); 200 | } else { 201 | mWaveformData = null; 202 | } 203 | } 204 | 205 | /** 206 | * Get the id of the audio track 207 | * 208 | * @return The id of the audio track 209 | */ 210 | public String getId() { 211 | return mUniqueId; 212 | } 213 | 214 | /** 215 | * Get the filename for this audio track source. 216 | * 217 | * @return The filename as an absolute file name 218 | */ 219 | public String getFilename() { 220 | return mFilename; 221 | } 222 | 223 | /** 224 | * Get the number of audio channels in the source of this audio track 225 | * 226 | * @return The number of audio channels in the source of this audio track 227 | */ 228 | public int getAudioChannels() { 229 | return mAudioChannels; 230 | } 231 | 232 | /** 233 | * Get the audio codec of the source of this audio track 234 | * 235 | * @return The audio codec of the source of this audio track 236 | * {@link android.media.videoeditor.MediaProperties} 237 | */ 238 | public int getAudioType() { 239 | return mAudioType; 240 | } 241 | 242 | /** 243 | * Get the audio sample frequency of the audio track 244 | * 245 | * @return The audio sample frequency of the audio track 246 | */ 247 | public int getAudioSamplingFrequency() { 248 | return mAudioSamplingFrequency; 249 | } 250 | 251 | /** 252 | * Get the audio bitrate of the audio track 253 | * 254 | * @return The audio bitrate of the audio track 255 | */ 256 | public int getAudioBitrate() { 257 | return mAudioBitrate; 258 | } 259 | 260 | /** 261 | * Set the volume of this audio track as percentage of the volume in the 262 | * original audio source file. 263 | * 264 | * @param volumePercent Percentage of the volume to apply. If it is set to 265 | * 0, then volume becomes mute. It it is set to 100, then volume 266 | * is same as original volume. It it is set to 200, then volume 267 | * is doubled (provided that volume amplification is supported) 268 | * 269 | * @throws UnsupportedOperationException if volume amplification is 270 | * requested and is not supported. 271 | */ 272 | public void setVolume(int volumePercent) { 273 | if (volumePercent > MediaProperties.AUDIO_MAX_VOLUME_PERCENT) { 274 | throw new IllegalArgumentException("Volume set exceeds maximum allowed value"); 275 | } 276 | 277 | if (volumePercent < 0) { 278 | throw new IllegalArgumentException("Invalid Volume "); 279 | } 280 | 281 | /** 282 | * Force update of preview settings 283 | */ 284 | mMANativeHelper.setGeneratePreview(true); 285 | 286 | mVolumePercent = volumePercent; 287 | } 288 | 289 | /** 290 | * Get the volume of the audio track as percentage of the volume in the 291 | * original audio source file. 292 | * 293 | * @return The volume in percentage 294 | */ 295 | public int getVolume() { 296 | return mVolumePercent; 297 | } 298 | 299 | /** 300 | * Mute/Unmute the audio track 301 | * 302 | * @param muted true to mute the audio track. SetMute(true) will make 303 | * the volume of this Audio Track to 0. 304 | */ 305 | public void setMute(boolean muted) { 306 | /** 307 | * Force update of preview settings 308 | */ 309 | mMANativeHelper.setGeneratePreview(true); 310 | mMuted = muted; 311 | } 312 | 313 | /** 314 | * Check if the audio track is muted 315 | * 316 | * @return true if the audio track is muted 317 | */ 318 | public boolean isMuted() { 319 | return mMuted; 320 | } 321 | 322 | /** 323 | * Get the start time of this audio track relative to the storyboard 324 | * timeline. 325 | * 326 | * @return The start time in milliseconds 327 | */ 328 | 329 | public long getStartTime() { 330 | return mStartTimeMs; 331 | } 332 | 333 | /** 334 | * Get the audio track duration 335 | * 336 | * @return The duration in milliseconds. This value represents actual audio 337 | * track duration. This value is not effected by 'enableLoop' or 338 | * 'setExtractBoundaries'. 339 | */ 340 | public long getDuration() { 341 | return mDurationMs; 342 | } 343 | 344 | /** 345 | * Get the audio track timeline duration 346 | * 347 | * @return The timeline duration as defined by the begin and end boundaries 348 | */ 349 | public long getTimelineDuration() { 350 | return mTimelineDurationMs; 351 | } 352 | 353 | /** 354 | * Sets the start and end marks for trimming an audio track 355 | * 356 | * @param beginMs start time in the audio track in milliseconds (relative to 357 | * the beginning of the audio track) 358 | * @param endMs end time in the audio track in milliseconds (relative to the 359 | * beginning of the audio track) 360 | */ 361 | public void setExtractBoundaries(long beginMs, long endMs) { 362 | if (beginMs > mDurationMs) { 363 | throw new IllegalArgumentException("Invalid start time"); 364 | } 365 | if (endMs > mDurationMs) { 366 | throw new IllegalArgumentException("Invalid end time"); 367 | } 368 | if (beginMs < 0) { 369 | throw new IllegalArgumentException("Invalid start time; is < 0"); 370 | } 371 | if (endMs < 0) { 372 | throw new IllegalArgumentException("Invalid end time; is < 0"); 373 | } 374 | 375 | /** 376 | * Force update of preview settings 377 | */ 378 | mMANativeHelper.setGeneratePreview(true); 379 | 380 | mBeginBoundaryTimeMs = beginMs; 381 | mEndBoundaryTimeMs = endMs; 382 | 383 | mTimelineDurationMs = mEndBoundaryTimeMs - mBeginBoundaryTimeMs; 384 | } 385 | 386 | /** 387 | * Get the boundary begin time 388 | * 389 | * @return The boundary begin time 390 | */ 391 | public long getBoundaryBeginTime() { 392 | return mBeginBoundaryTimeMs; 393 | } 394 | 395 | /** 396 | * Get the boundary end time 397 | * 398 | * @return The boundary end time 399 | */ 400 | public long getBoundaryEndTime() { 401 | return mEndBoundaryTimeMs; 402 | } 403 | 404 | /** 405 | * Enable the loop mode for this audio track. Note that only one of the 406 | * audio tracks in the timeline can have the loop mode enabled. When looping 407 | * is enabled the samples between mBeginBoundaryTimeMs and 408 | * mEndBoundaryTimeMs are looped. 409 | */ 410 | public void enableLoop() { 411 | if (!mLoop) { 412 | /** 413 | * Force update of preview settings 414 | */ 415 | mMANativeHelper.setGeneratePreview(true); 416 | mLoop = true; 417 | } 418 | } 419 | 420 | /** 421 | * Disable the loop mode 422 | */ 423 | public void disableLoop() { 424 | if (mLoop) { 425 | /** 426 | * Force update of preview settings 427 | */ 428 | mMANativeHelper.setGeneratePreview(true); 429 | mLoop = false; 430 | } 431 | } 432 | 433 | /** 434 | * Check if looping is enabled 435 | * 436 | * @return true if looping is enabled 437 | */ 438 | public boolean isLooping() { 439 | return mLoop; 440 | } 441 | 442 | /** 443 | * Disable the audio duck effect 444 | */ 445 | public void disableDucking() { 446 | if (mIsDuckingEnabled) { 447 | /** 448 | * Force update of preview settings 449 | */ 450 | mMANativeHelper.setGeneratePreview(true); 451 | mIsDuckingEnabled = false; 452 | } 453 | } 454 | 455 | /** 456 | * Enable ducking by specifying the required parameters 457 | * 458 | * @param threshold Ducking will be activated when the energy in 459 | * the media items audio signal goes above this value. The valid 460 | * range of values is 0db to 90dB. 0dB is equivalent to disabling 461 | * ducking. 462 | * @param duckedTrackVolume The relative volume of the audio track when ducking 463 | * is active. The valid range of values is 0 to 100. 464 | */ 465 | public void enableDucking(int threshold, int duckedTrackVolume) { 466 | if (threshold < 0 || threshold > 90) { 467 | throw new IllegalArgumentException("Invalid threshold value: " + threshold); 468 | } 469 | 470 | if (duckedTrackVolume < 0 || duckedTrackVolume > 100) { 471 | throw new IllegalArgumentException("Invalid duckedTrackVolume value: " 472 | + duckedTrackVolume); 473 | } 474 | 475 | /** 476 | * Force update of preview settings 477 | */ 478 | mMANativeHelper.setGeneratePreview(true); 479 | 480 | mDuckingThreshold = threshold; 481 | mDuckedTrackVolume = duckedTrackVolume; 482 | mIsDuckingEnabled = true; 483 | } 484 | 485 | /** 486 | * Check if ducking is enabled 487 | * 488 | * @return true if ducking is enabled 489 | */ 490 | public boolean isDuckingEnabled() { 491 | return mIsDuckingEnabled; 492 | } 493 | 494 | /** 495 | * Get the ducking threshold. 496 | * 497 | * @return The ducking threshold 498 | */ 499 | public int getDuckingThreshhold() { 500 | return mDuckingThreshold; 501 | } 502 | 503 | /** 504 | * Get the ducked track volume. 505 | * 506 | * @return The ducked track volume 507 | */ 508 | public int getDuckedTrackVolume() { 509 | return mDuckedTrackVolume; 510 | } 511 | 512 | /** 513 | * This API allows to generate a file containing the sample volume levels of 514 | * this audio track object. This function may take significant time and is 515 | * blocking. The filename can be retrieved using getAudioWaveformFilename(). 516 | * 517 | * @param listener The progress listener 518 | * 519 | * @throws IOException if the output file cannot be created 520 | * @throws IllegalArgumentException if the audio file does not have a valid 521 | * audio track 522 | * @throws IllegalStateException if the codec type is unsupported 523 | */ 524 | public void extractAudioWaveform(ExtractAudioWaveformProgressListener listener) 525 | throws IOException { 526 | if (mAudioWaveformFilename == null) { 527 | /** 528 | * AudioWaveformFilename is generated 529 | */ 530 | final String projectPath = mMANativeHelper.getProjectPath(); 531 | final String audioWaveFilename = String.format(projectPath + "/audioWaveformFile-" 532 | + getId() + ".dat"); 533 | 534 | /** 535 | * Logic to get frame duration = (no. of frames per sample * 1000)/ 536 | * sampling frequency 537 | */ 538 | final int frameDuration; 539 | final int sampleCount; 540 | final int codecType = mMANativeHelper.getAudioCodecType(mAudioType); 541 | switch (codecType) { 542 | case MediaProperties.ACODEC_AMRNB: { 543 | frameDuration = (MediaProperties.SAMPLES_PER_FRAME_AMRNB * 1000) 544 | / MediaProperties.DEFAULT_SAMPLING_FREQUENCY; 545 | sampleCount = MediaProperties.SAMPLES_PER_FRAME_AMRNB; 546 | break; 547 | } 548 | 549 | case MediaProperties.ACODEC_AMRWB: { 550 | frameDuration = (MediaProperties.SAMPLES_PER_FRAME_AMRWB * 1000) 551 | / MediaProperties.DEFAULT_SAMPLING_FREQUENCY; 552 | sampleCount = MediaProperties.SAMPLES_PER_FRAME_AMRWB; 553 | break; 554 | } 555 | 556 | case MediaProperties.ACODEC_AAC_LC: { 557 | frameDuration = (MediaProperties.SAMPLES_PER_FRAME_AAC * 1000) 558 | / MediaProperties.DEFAULT_SAMPLING_FREQUENCY; 559 | sampleCount = MediaProperties.SAMPLES_PER_FRAME_AAC; 560 | break; 561 | } 562 | 563 | case MediaProperties.ACODEC_MP3: { 564 | frameDuration = (MediaProperties.SAMPLES_PER_FRAME_MP3 * 1000) 565 | / MediaProperties.DEFAULT_SAMPLING_FREQUENCY; 566 | sampleCount = MediaProperties.SAMPLES_PER_FRAME_MP3; 567 | break; 568 | } 569 | 570 | default: { 571 | throw new IllegalStateException("Unsupported codec type: " 572 | + codecType); 573 | } 574 | } 575 | 576 | mMANativeHelper.generateAudioGraph( mUniqueId, 577 | mFilename, 578 | audioWaveFilename, 579 | frameDuration, 580 | MediaProperties.DEFAULT_CHANNEL_COUNT, 581 | sampleCount, 582 | listener, 583 | false); 584 | /** 585 | * Record the generated file name 586 | */ 587 | mAudioWaveformFilename = audioWaveFilename; 588 | } 589 | mWaveformData = new SoftReference(new WaveformData(mAudioWaveformFilename)); 590 | } 591 | 592 | /** 593 | * Get the audio waveform file name if extractAudioWaveform was successful. 594 | * 595 | * @return the name of the file, null if the file does not exist 596 | */ 597 | String getAudioWaveformFilename() { 598 | return mAudioWaveformFilename; 599 | } 600 | 601 | /** 602 | * Delete the waveform file 603 | */ 604 | void invalidate() { 605 | if (mAudioWaveformFilename != null) { 606 | new File(mAudioWaveformFilename).delete(); 607 | mAudioWaveformFilename = null; 608 | mWaveformData = null; 609 | } 610 | } 611 | 612 | /** 613 | * Get the audio waveform data. 614 | * 615 | * @return The waveform data 616 | * 617 | * @throws IOException if the waveform file cannot be found 618 | */ 619 | public WaveformData getWaveformData() throws IOException { 620 | if (mWaveformData == null) { 621 | return null; 622 | } 623 | 624 | WaveformData waveformData = mWaveformData.get(); 625 | if (waveformData != null) { 626 | return waveformData; 627 | } else if (mAudioWaveformFilename != null) { 628 | try { 629 | waveformData = new WaveformData(mAudioWaveformFilename); 630 | } catch (IOException e) { 631 | throw e; 632 | } 633 | mWaveformData = new SoftReference(waveformData); 634 | return waveformData; 635 | } else { 636 | return null; 637 | } 638 | } 639 | 640 | /* 641 | * {@inheritDoc} 642 | */ 643 | @Override 644 | public boolean equals(Object object) { 645 | if (!(object instanceof AudioTrack)) { 646 | return false; 647 | } 648 | return mUniqueId.equals(((AudioTrack)object).mUniqueId); 649 | } 650 | 651 | /* 652 | * {@inheritDoc} 653 | */ 654 | @Override 655 | public int hashCode() { 656 | return mUniqueId.hashCode(); 657 | } 658 | } 659 | -------------------------------------------------------------------------------- /src/android/media/videoeditor/Effect.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 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 | 17 | 18 | package android.media.videoeditor; 19 | 20 | /** 21 | * This is the super class for all effects. An effect can only be applied to a 22 | * single media item. 23 | * {@hide} 24 | */ 25 | public abstract class Effect { 26 | /** 27 | * Instance variables 28 | */ 29 | private final String mUniqueId; 30 | /** 31 | * The effect owner 32 | */ 33 | private final MediaItem mMediaItem; 34 | 35 | protected long mDurationMs; 36 | /** 37 | * The start time of the effect relative to the beginning 38 | * of the media item 39 | */ 40 | protected long mStartTimeMs; 41 | 42 | /** 43 | * Default constructor 44 | */ 45 | @SuppressWarnings("unused") 46 | private Effect() { 47 | mMediaItem = null; 48 | mUniqueId = null; 49 | mStartTimeMs = 0; 50 | mDurationMs = 0; 51 | } 52 | 53 | /** 54 | * Constructor 55 | * 56 | * @param mediaItem The media item owner 57 | * @param effectId The effect id 58 | * @param startTimeMs The start time relative to the media item to which it 59 | * is applied 60 | * @param durationMs The effect duration in milliseconds 61 | */ 62 | public Effect(MediaItem mediaItem, String effectId, long startTimeMs, 63 | long durationMs) { 64 | if (mediaItem == null) { 65 | throw new IllegalArgumentException("Media item cannot be null"); 66 | } 67 | 68 | if ((startTimeMs < 0) || (durationMs < 0)) { 69 | throw new IllegalArgumentException("Invalid start time Or/And Duration"); 70 | } 71 | if (startTimeMs + durationMs > mediaItem.getDuration()) { 72 | throw new IllegalArgumentException("Invalid start time and duration"); 73 | } 74 | 75 | mMediaItem = mediaItem; 76 | mUniqueId = effectId; 77 | mStartTimeMs = startTimeMs; 78 | mDurationMs = durationMs; 79 | } 80 | 81 | /** 82 | * Get the id of the effect. 83 | * 84 | * @return The id of the effect 85 | */ 86 | public String getId() { 87 | return mUniqueId; 88 | } 89 | 90 | /** 91 | * Set the duration of the effect. If a preview or export is in progress, 92 | * then this change is effective for next preview or export session. 93 | * 94 | * @param durationMs of the effect in milliseconds 95 | */ 96 | public void setDuration(long durationMs) { 97 | if (durationMs <0) { 98 | throw new IllegalArgumentException("Invalid duration"); 99 | } 100 | 101 | if (mStartTimeMs + durationMs > mMediaItem.getDuration()) { 102 | throw new IllegalArgumentException("Duration is too large"); 103 | } 104 | 105 | getMediaItem().getNativeContext().setGeneratePreview(true); 106 | 107 | final long oldDurationMs = mDurationMs; 108 | mDurationMs = durationMs; 109 | 110 | mMediaItem.invalidateTransitions(mStartTimeMs, oldDurationMs, mStartTimeMs, mDurationMs); 111 | } 112 | 113 | /** 114 | * Get the duration of the effect 115 | * 116 | * @return The duration of the effect in milliseconds 117 | */ 118 | public long getDuration() { 119 | return mDurationMs; 120 | } 121 | 122 | /** 123 | * Set start time of the effect. If a preview or export is in progress, then 124 | * this change is effective for next preview or export session. 125 | * 126 | * @param startTimeMs The start time of the effect relative to the beginning 127 | * of the media item in milliseconds 128 | */ 129 | public void setStartTime(long startTimeMs) { 130 | if (startTimeMs + mDurationMs > mMediaItem.getDuration()) { 131 | throw new IllegalArgumentException("Start time is too large"); 132 | } 133 | 134 | getMediaItem().getNativeContext().setGeneratePreview(true); 135 | final long oldStartTimeMs = mStartTimeMs; 136 | mStartTimeMs = startTimeMs; 137 | 138 | mMediaItem.invalidateTransitions(oldStartTimeMs, mDurationMs, mStartTimeMs, mDurationMs); 139 | } 140 | 141 | /** 142 | * Get the start time of the effect 143 | * 144 | * @return The start time in milliseconds 145 | */ 146 | public long getStartTime() { 147 | return mStartTimeMs; 148 | } 149 | 150 | /** 151 | * Set the start time and duration 152 | * 153 | * @param startTimeMs start time in milliseconds 154 | * @param durationMs The duration in milliseconds 155 | */ 156 | public void setStartTimeAndDuration(long startTimeMs, long durationMs) { 157 | if (startTimeMs + durationMs > mMediaItem.getDuration()) { 158 | throw new IllegalArgumentException("Invalid start time or duration"); 159 | } 160 | 161 | getMediaItem().getNativeContext().setGeneratePreview(true); 162 | final long oldStartTimeMs = mStartTimeMs; 163 | final long oldDurationMs = mDurationMs; 164 | 165 | mStartTimeMs = startTimeMs; 166 | mDurationMs = durationMs; 167 | 168 | mMediaItem.invalidateTransitions(oldStartTimeMs, oldDurationMs, mStartTimeMs, mDurationMs); 169 | } 170 | 171 | /** 172 | * Get the media item owner. 173 | * 174 | * @return The media item owner 175 | */ 176 | public MediaItem getMediaItem() { 177 | return mMediaItem; 178 | } 179 | 180 | /* 181 | * {@inheritDoc} 182 | */ 183 | @Override 184 | public boolean equals(Object object) { 185 | if (!(object instanceof Effect)) { 186 | return false; 187 | } 188 | return mUniqueId.equals(((Effect)object).mUniqueId); 189 | } 190 | 191 | /* 192 | * {@inheritDoc} 193 | */ 194 | @Override 195 | public int hashCode() { 196 | return mUniqueId.hashCode(); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/android/media/videoeditor/EffectColor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 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 | 17 | 18 | package android.media.videoeditor; 19 | 20 | /** 21 | * This class allows to apply color effect on a media item. 22 | * {@hide} 23 | */ 24 | public class EffectColor extends Effect { 25 | 26 | /** 27 | * Change the video frame color to the RGB color value provided 28 | */ 29 | public static final int TYPE_COLOR = 1; 30 | /** 31 | * Change the video frame color to a gradation from RGB color (at the top of 32 | * the frame) to black (at the bottom of the frame). 33 | */ 34 | public static final int TYPE_GRADIENT = 2; 35 | /** 36 | * Change the video frame color to sepia 37 | */ 38 | public static final int TYPE_SEPIA = 3; 39 | /** 40 | * Invert the video frame color 41 | */ 42 | public static final int TYPE_NEGATIVE = 4; 43 | /** 44 | * Make the video look like as if it was recorded in 50's 45 | */ 46 | public static final int TYPE_FIFTIES = 5; 47 | /** 48 | * Change the video frame color to the RGB color value GREEN 49 | */ 50 | public static final int GREEN = 0x0000ff00; 51 | /** 52 | * Change the video frame color to the RGB color value PINK 53 | */ 54 | public static final int PINK = 0x00ff66cc; 55 | /** 56 | * Change the video frame color to the RGB color value GRAY 57 | */ 58 | public static final int GRAY = 0x007f7f7f; 59 | 60 | /** 61 | * The effect type 62 | */ 63 | private final int mType; 64 | 65 | /** 66 | * The effect color 67 | */ 68 | private final int mColor; 69 | 70 | /** 71 | * An object of this type cannot be instantiated by using the default 72 | * constructor 73 | */ 74 | @SuppressWarnings("unused") 75 | private EffectColor() { 76 | this(null, null, 0, 0, 0, 0); 77 | } 78 | 79 | /** 80 | * Constructor 81 | * 82 | * @param mediaItem The media item owner 83 | * @param effectId The effect id 84 | * @param startTimeMs The start time relative to the media item to which it 85 | * is applied 86 | * @param durationMs The duration of this effect in milliseconds 87 | * @param type type of the effect. type is one of: TYPE_COLOR, 88 | * TYPE_GRADIENT, TYPE_SEPIA, TYPE_NEGATIVE, TYPE_FIFTIES. 89 | * @param color If type is TYPE_COLOR, color is the RGB color as 888. 90 | * If type is TYPE_GRADIENT, color is the RGB color at the 91 | * top of the frame. Otherwise, color is ignored 92 | */ 93 | public EffectColor(MediaItem mediaItem, String effectId, long startTimeMs, 94 | long durationMs, int type, int color) { 95 | super(mediaItem, effectId, startTimeMs, durationMs); 96 | switch (type) { 97 | case TYPE_COLOR: 98 | case TYPE_GRADIENT: { 99 | switch (color) { 100 | case GREEN: 101 | case PINK: 102 | case GRAY: 103 | mColor = color; 104 | break; 105 | 106 | default: 107 | throw new IllegalArgumentException("Invalid Color: " + color); 108 | } 109 | break; 110 | } 111 | case TYPE_SEPIA: 112 | case TYPE_NEGATIVE: 113 | case TYPE_FIFTIES: { 114 | mColor = -1; 115 | break; 116 | } 117 | 118 | default: { 119 | throw new IllegalArgumentException("Invalid type: " + type); 120 | } 121 | } 122 | mType = type; 123 | } 124 | 125 | /** 126 | * Get the effect type. 127 | * 128 | * @return The effect type 129 | */ 130 | public int getType() { 131 | return mType; 132 | } 133 | 134 | /** 135 | * Get the color if effect type is TYPE_COLOR or TYPE_GRADIENT. 136 | * 137 | * @return the color as RGB 888 if type is TYPE_COLOR or TYPE_GRADIENT. 138 | */ 139 | public int getColor() { 140 | return mColor; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/android/media/videoeditor/EffectKenBurns.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 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 | 17 | 18 | package android.media.videoeditor; 19 | 20 | import android.graphics.Rect; 21 | 22 | /** 23 | * This class represents a Ken Burns effect. 24 | * {@hide} 25 | */ 26 | public class EffectKenBurns extends Effect { 27 | /** 28 | * Instance variables 29 | */ 30 | private Rect mStartRect; 31 | private Rect mEndRect; 32 | 33 | /** 34 | * Objects of this type cannot be instantiated by using the default 35 | * constructor 36 | */ 37 | @SuppressWarnings("unused") 38 | private EffectKenBurns() { 39 | this(null, null, null, null, 0, 0); 40 | } 41 | 42 | /** 43 | * Constructor 44 | * 45 | * @param mediaItem The media item owner 46 | * @param effectId The effect id 47 | * @param startRect The start rectangle 48 | * @param endRect The end rectangle 49 | * @param startTimeMs The start time 50 | * @param durationMs The duration of the Ken Burns effect in milliseconds 51 | */ 52 | public EffectKenBurns(MediaItem mediaItem, String effectId, Rect startRect, 53 | Rect endRect, long startTimeMs, long durationMs) { 54 | super(mediaItem, effectId, startTimeMs, durationMs); 55 | 56 | if ( (startRect.width() <= 0) || (startRect.height() <= 0) ) { 57 | throw new IllegalArgumentException("Invalid Start rectangle"); 58 | } 59 | if ( (endRect.width() <= 0) || (endRect.height() <= 0) ) { 60 | throw new IllegalArgumentException("Invalid End rectangle"); 61 | } 62 | 63 | mStartRect = startRect; 64 | mEndRect = endRect; 65 | } 66 | 67 | 68 | /** 69 | * Get the start rectangle. 70 | * 71 | * @return The start rectangle 72 | */ 73 | public Rect getStartRect() { 74 | return mStartRect; 75 | } 76 | 77 | 78 | /** 79 | * Get the end rectangle. 80 | * 81 | * @return The end rectangle 82 | */ 83 | public Rect getEndRect() { 84 | return mEndRect; 85 | } 86 | 87 | /** 88 | * Get the KenBurn effect start and end rectangle coordinates 89 | * @param start The rect object to be populated with start 90 | * rectangle coordinates 91 | * 92 | * @param end The rect object to be populated with end 93 | * rectangle coordinates 94 | */ 95 | void getKenBurnsSettings(Rect start, Rect end) { 96 | start.left = getStartRect().left; 97 | start.top = getStartRect().top; 98 | start.right = getStartRect().right; 99 | start.bottom = getStartRect().bottom; 100 | end.left = getEndRect().left; 101 | end.top = getEndRect().top; 102 | end.right = getEndRect().right; 103 | end.bottom = getEndRect().bottom; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/android/media/videoeditor/ExtractAudioWaveformProgressListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 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 | 17 | 18 | package android.media.videoeditor; 19 | 20 | /** 21 | * This listener interface is used by 22 | * {@link MediaVideoItem#extractAudioWaveform(ExtractAudioWaveformProgressListener listener)} 23 | * or 24 | * {@link AudioTrack#extractAudioWaveform(ExtractAudioWaveformProgressListener listener)} 25 | * {@hide} 26 | */ 27 | public interface ExtractAudioWaveformProgressListener { 28 | /** 29 | * This method notifies the listener of the progress status of 30 | * an extractAudioWaveform operation. 31 | * This method may be called maximum 100 times for one operation. 32 | * 33 | * @param progress The progress in %. At the beginning of the operation, 34 | * this value is set to 0; at the end, the value is set to 100. 35 | */ 36 | public void onProgress(int progress); 37 | } 38 | 39 | -------------------------------------------------------------------------------- /src/android/media/videoeditor/MediaItem.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 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 | 17 | 18 | package android.media.videoeditor; 19 | 20 | import java.io.File; 21 | import java.io.FileNotFoundException; 22 | import java.io.FileOutputStream; 23 | import java.io.IOException; 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | 27 | import java.io.DataOutputStream; 28 | import java.nio.ByteBuffer; 29 | import java.nio.IntBuffer; 30 | 31 | import android.graphics.Bitmap; 32 | import android.media.videoeditor.MediaArtistNativeHelper.ClipSettings; 33 | import android.media.videoeditor.MediaArtistNativeHelper.FileType; 34 | import android.media.videoeditor.MediaArtistNativeHelper.MediaRendering; 35 | 36 | /** 37 | * This abstract class describes the base class for any MediaItem. Objects are 38 | * defined with a file path as a source data. 39 | * {@hide} 40 | */ 41 | public abstract class MediaItem { 42 | /** 43 | * A constant which can be used to specify the end of the file (instead of 44 | * providing the actual duration of the media item). 45 | */ 46 | public final static int END_OF_FILE = -1; 47 | 48 | /** 49 | * Rendering modes 50 | */ 51 | /** 52 | * When using the RENDERING_MODE_BLACK_BORDER rendering mode video frames 53 | * are resized by preserving the aspect ratio until the movie matches one of 54 | * the dimensions of the output movie. The areas outside the resized video 55 | * clip are rendered black. 56 | */ 57 | public static final int RENDERING_MODE_BLACK_BORDER = 0; 58 | 59 | /** 60 | * When using the RENDERING_MODE_STRETCH rendering mode video frames are 61 | * stretched horizontally or vertically to match the current aspect ratio of 62 | * the video editor. 63 | */ 64 | public static final int RENDERING_MODE_STRETCH = 1; 65 | 66 | /** 67 | * When using the RENDERING_MODE_CROPPING rendering mode video frames are 68 | * scaled horizontally or vertically by preserving the original aspect ratio 69 | * of the media item. 70 | */ 71 | public static final int RENDERING_MODE_CROPPING = 2; 72 | 73 | /** 74 | * The unique id of the MediaItem 75 | */ 76 | private final String mUniqueId; 77 | 78 | /** 79 | * The name of the file associated with the MediaItem 80 | */ 81 | protected final String mFilename; 82 | 83 | /** 84 | * List of effects 85 | */ 86 | private final List mEffects; 87 | 88 | /** 89 | * List of overlays 90 | */ 91 | private final List mOverlays; 92 | 93 | /** 94 | * The rendering mode 95 | */ 96 | private int mRenderingMode; 97 | 98 | private final MediaArtistNativeHelper mMANativeHelper; 99 | 100 | private final String mProjectPath; 101 | 102 | /** 103 | * Beginning and end transitions 104 | */ 105 | protected Transition mBeginTransition; 106 | 107 | protected Transition mEndTransition; 108 | 109 | protected String mGeneratedImageClip; 110 | 111 | protected boolean mRegenerateClip; 112 | 113 | private boolean mBlankFrameGenerated = false; 114 | 115 | private String mBlankFrameFilename = null; 116 | 117 | /** 118 | * Constructor 119 | * 120 | * @param editor The video editor reference 121 | * @param mediaItemId The MediaItem id 122 | * @param filename name of the media file. 123 | * @param renderingMode The rendering mode 124 | * @throws IOException if file is not found 125 | * @throws IllegalArgumentException if a capability such as file format is 126 | * not supported the exception object contains the unsupported 127 | * capability 128 | */ 129 | protected MediaItem(VideoEditor editor, String mediaItemId, String filename, 130 | int renderingMode) throws IOException { 131 | if (filename == null) { 132 | throw new IllegalArgumentException("MediaItem : filename is null"); 133 | } 134 | File file = new File(filename); 135 | if (!file.exists()) { 136 | throw new IOException(filename + " not found ! "); 137 | } 138 | 139 | /*Compare file_size with 2GB*/ 140 | if (VideoEditor.MAX_SUPPORTED_FILE_SIZE <= file.length()) { 141 | throw new IllegalArgumentException("File size is more than 2GB"); 142 | } 143 | mUniqueId = mediaItemId; 144 | mFilename = filename; 145 | mRenderingMode = renderingMode; 146 | mEffects = new ArrayList(); 147 | mOverlays = new ArrayList(); 148 | mBeginTransition = null; 149 | mEndTransition = null; 150 | mMANativeHelper = ((VideoEditorImpl)editor).getNativeContext(); 151 | mProjectPath = editor.getPath(); 152 | mRegenerateClip = false; 153 | mGeneratedImageClip = null; 154 | } 155 | 156 | /** 157 | * @return The id of the media item 158 | */ 159 | public String getId() { 160 | return mUniqueId; 161 | } 162 | 163 | /** 164 | * @return The media source file name 165 | */ 166 | public String getFilename() { 167 | return mFilename; 168 | } 169 | 170 | /** 171 | * If aspect ratio of the MediaItem is different from the aspect ratio of 172 | * the editor then this API controls the rendering mode. 173 | * 174 | * @param renderingMode rendering mode. It is one of: 175 | * {@link #RENDERING_MODE_BLACK_BORDER}, 176 | * {@link #RENDERING_MODE_STRETCH} 177 | */ 178 | public void setRenderingMode(int renderingMode) { 179 | switch (renderingMode) { 180 | case RENDERING_MODE_BLACK_BORDER: 181 | case RENDERING_MODE_STRETCH: 182 | case RENDERING_MODE_CROPPING: 183 | break; 184 | 185 | default: 186 | throw new IllegalArgumentException("Invalid Rendering Mode"); 187 | } 188 | 189 | mMANativeHelper.setGeneratePreview(true); 190 | 191 | mRenderingMode = renderingMode; 192 | if (mBeginTransition != null) { 193 | mBeginTransition.invalidate(); 194 | } 195 | 196 | if (mEndTransition != null) { 197 | mEndTransition.invalidate(); 198 | } 199 | 200 | for (Overlay overlay : mOverlays) { 201 | ((OverlayFrame)overlay).invalidateGeneratedFiles(); 202 | } 203 | } 204 | 205 | /** 206 | * @return The rendering mode 207 | */ 208 | public int getRenderingMode() { 209 | return mRenderingMode; 210 | } 211 | 212 | /** 213 | * @param transition The beginning transition 214 | */ 215 | void setBeginTransition(Transition transition) { 216 | mBeginTransition = transition; 217 | } 218 | 219 | /** 220 | * @return The begin transition 221 | */ 222 | public Transition getBeginTransition() { 223 | return mBeginTransition; 224 | } 225 | 226 | /** 227 | * @param transition The end transition 228 | */ 229 | void setEndTransition(Transition transition) { 230 | mEndTransition = transition; 231 | } 232 | 233 | /** 234 | * @return The end transition 235 | */ 236 | public Transition getEndTransition() { 237 | return mEndTransition; 238 | } 239 | 240 | /** 241 | * @return The timeline duration. This is the actual duration in the 242 | * timeline (trimmed duration) 243 | */ 244 | public abstract long getTimelineDuration(); 245 | 246 | /** 247 | * @return The is the full duration of the media item (not trimmed) 248 | */ 249 | public abstract long getDuration(); 250 | 251 | /** 252 | * @return The source file type 253 | */ 254 | public abstract int getFileType(); 255 | 256 | /** 257 | * @return Get the native width of the media item 258 | */ 259 | public abstract int getWidth(); 260 | 261 | /** 262 | * @return Get the native height of the media item 263 | */ 264 | public abstract int getHeight(); 265 | 266 | /** 267 | * Get aspect ratio of the source media item. 268 | * 269 | * @return the aspect ratio as described in MediaProperties. 270 | * MediaProperties.ASPECT_RATIO_UNDEFINED if aspect ratio is not 271 | * supported as in MediaProperties 272 | */ 273 | public abstract int getAspectRatio(); 274 | 275 | /** 276 | * Add the specified effect to this media item. 277 | * 278 | * Note that certain types of effects cannot be applied to video and to 279 | * image media items. For example in certain implementation a Ken Burns 280 | * implementation cannot be applied to video media item. 281 | * 282 | * This method invalidates transition video clips if the 283 | * effect overlaps with the beginning and/or the end transition. 284 | * 285 | * @param effect The effect to apply 286 | * @throws IllegalStateException if a preview or an export is in progress 287 | * @throws IllegalArgumentException if the effect start and/or duration are 288 | * invalid or if the effect cannot be applied to this type of media 289 | * item or if the effect id is not unique across all the Effects 290 | * added. 291 | */ 292 | public void addEffect(Effect effect) { 293 | 294 | if (effect == null) { 295 | throw new IllegalArgumentException("NULL effect cannot be applied"); 296 | } 297 | 298 | if (effect.getMediaItem() != this) { 299 | throw new IllegalArgumentException("Media item mismatch"); 300 | } 301 | 302 | if (mEffects.contains(effect)) { 303 | throw new IllegalArgumentException("Effect already exists: " + effect.getId()); 304 | } 305 | 306 | if (effect.getStartTime() + effect.getDuration() > getDuration()) { 307 | throw new IllegalArgumentException( 308 | "Effect start time + effect duration > media clip duration"); 309 | } 310 | 311 | mMANativeHelper.setGeneratePreview(true); 312 | 313 | mEffects.add(effect); 314 | 315 | invalidateTransitions(effect.getStartTime(), effect.getDuration()); 316 | 317 | if (effect instanceof EffectKenBurns) { 318 | mRegenerateClip = true; 319 | } 320 | } 321 | 322 | /** 323 | * Remove the effect with the specified id. 324 | * 325 | * This method invalidates a transition video clip if the effect overlaps 326 | * with a transition. 327 | * 328 | * @param effectId The id of the effect to be removed 329 | * 330 | * @return The effect that was removed 331 | * @throws IllegalStateException if a preview or an export is in progress 332 | */ 333 | public Effect removeEffect(String effectId) { 334 | for (Effect effect : mEffects) { 335 | if (effect.getId().equals(effectId)) { 336 | mMANativeHelper.setGeneratePreview(true); 337 | 338 | mEffects.remove(effect); 339 | 340 | invalidateTransitions(effect.getStartTime(), effect.getDuration()); 341 | if (effect instanceof EffectKenBurns) { 342 | if (mGeneratedImageClip != null) { 343 | /** 344 | * Delete the file 345 | */ 346 | new File(mGeneratedImageClip).delete(); 347 | /** 348 | * Invalidate the filename 349 | */ 350 | mGeneratedImageClip = null; 351 | } 352 | mRegenerateClip = false; 353 | } 354 | return effect; 355 | } 356 | } 357 | return null; 358 | } 359 | 360 | /** 361 | * Set the filepath of the generated image clip when the effect is added. 362 | * 363 | * @param The filepath of the generated image clip. 364 | */ 365 | void setGeneratedImageClip(String generatedFilePath) { 366 | mGeneratedImageClip = generatedFilePath; 367 | } 368 | 369 | /** 370 | * Get the filepath of the generated image clip when the effect is added. 371 | * 372 | * @return The filepath of the generated image clip (null if it does not 373 | * exist) 374 | */ 375 | String getGeneratedImageClip() { 376 | return mGeneratedImageClip; 377 | } 378 | 379 | /** 380 | * Find the effect with the specified id 381 | * 382 | * @param effectId The effect id 383 | * @return The effect with the specified id (null if it does not exist) 384 | */ 385 | public Effect getEffect(String effectId) { 386 | for (Effect effect : mEffects) { 387 | if (effect.getId().equals(effectId)) { 388 | return effect; 389 | } 390 | } 391 | return null; 392 | } 393 | 394 | /** 395 | * Get the list of effects. 396 | * 397 | * @return the effects list. If no effects exist an empty list will be 398 | * returned. 399 | */ 400 | public List getAllEffects() { 401 | return mEffects; 402 | } 403 | 404 | /** 405 | * Add an overlay to the storyboard. This method invalidates a transition 406 | * video clip if the overlay overlaps with a transition. 407 | * 408 | * @param overlay The overlay to add 409 | * @throws IllegalStateException if a preview or an export is in progress or 410 | * if the overlay id is not unique across all the overlays added 411 | * or if the bitmap is not specified or if the dimensions of the 412 | * bitmap do not match the dimensions of the media item 413 | * @throws FileNotFoundException, IOException if overlay could not be saved 414 | * to project path 415 | */ 416 | public void addOverlay(Overlay overlay) throws FileNotFoundException, IOException { 417 | if (overlay == null) { 418 | throw new IllegalArgumentException("NULL Overlay cannot be applied"); 419 | } 420 | 421 | if (overlay.getMediaItem() != this) { 422 | throw new IllegalArgumentException("Media item mismatch"); 423 | } 424 | 425 | if (mOverlays.contains(overlay)) { 426 | throw new IllegalArgumentException("Overlay already exists: " + overlay.getId()); 427 | } 428 | 429 | if (overlay.getStartTime() + overlay.getDuration() > getDuration()) { 430 | throw new IllegalArgumentException( 431 | "Overlay start time + overlay duration > media clip duration"); 432 | } 433 | 434 | if (overlay instanceof OverlayFrame) { 435 | final OverlayFrame frame = (OverlayFrame)overlay; 436 | final Bitmap bitmap = frame.getBitmap(); 437 | if (bitmap == null) { 438 | throw new IllegalArgumentException("Overlay bitmap not specified"); 439 | } 440 | 441 | final int scaledWidth, scaledHeight; 442 | if (this instanceof MediaVideoItem) { 443 | scaledWidth = getWidth(); 444 | scaledHeight = getHeight(); 445 | } else { 446 | scaledWidth = ((MediaImageItem)this).getScaledWidth(); 447 | scaledHeight = ((MediaImageItem)this).getScaledHeight(); 448 | } 449 | 450 | /** 451 | * The dimensions of the overlay bitmap must be the same as the 452 | * media item dimensions 453 | */ 454 | if (bitmap.getWidth() != scaledWidth || bitmap.getHeight() != scaledHeight) { 455 | throw new IllegalArgumentException( 456 | "Bitmap dimensions must match media item dimensions"); 457 | } 458 | 459 | mMANativeHelper.setGeneratePreview(true); 460 | ((OverlayFrame)overlay).save(mProjectPath); 461 | 462 | mOverlays.add(overlay); 463 | invalidateTransitions(overlay.getStartTime(), overlay.getDuration()); 464 | 465 | } else { 466 | throw new IllegalArgumentException("Overlay not supported"); 467 | } 468 | } 469 | 470 | /** 471 | * @param flag The flag to indicate if regeneration of clip is true or 472 | * false. 473 | */ 474 | void setRegenerateClip(boolean flag) { 475 | mRegenerateClip = flag; 476 | } 477 | 478 | /** 479 | * @return flag The flag to indicate if regeneration of clip is true or 480 | * false. 481 | */ 482 | boolean getRegenerateClip() { 483 | return mRegenerateClip; 484 | } 485 | 486 | /** 487 | * Remove the overlay with the specified id. 488 | * 489 | * This method invalidates a transition video clip if the overlay overlaps 490 | * with a transition. 491 | * 492 | * @param overlayId The id of the overlay to be removed 493 | * 494 | * @return The overlay that was removed 495 | * @throws IllegalStateException if a preview or an export is in progress 496 | */ 497 | public Overlay removeOverlay(String overlayId) { 498 | for (Overlay overlay : mOverlays) { 499 | if (overlay.getId().equals(overlayId)) { 500 | mMANativeHelper.setGeneratePreview(true); 501 | 502 | mOverlays.remove(overlay); 503 | if (overlay instanceof OverlayFrame) { 504 | ((OverlayFrame)overlay).invalidate(); 505 | } 506 | invalidateTransitions(overlay.getStartTime(), overlay.getDuration()); 507 | return overlay; 508 | } 509 | } 510 | return null; 511 | } 512 | 513 | /** 514 | * Find the overlay with the specified id 515 | * 516 | * @param overlayId The overlay id 517 | * 518 | * @return The overlay with the specified id (null if it does not exist) 519 | */ 520 | public Overlay getOverlay(String overlayId) { 521 | for (Overlay overlay : mOverlays) { 522 | if (overlay.getId().equals(overlayId)) { 523 | return overlay; 524 | } 525 | } 526 | 527 | return null; 528 | } 529 | 530 | /** 531 | * Get the list of overlays associated with this media item 532 | * 533 | * Note that if any overlay source files are not accessible anymore, 534 | * this method will still provide the full list of overlays. 535 | * 536 | * @return The list of overlays. If no overlays exist an empty list will 537 | * be returned. 538 | */ 539 | public List getAllOverlays() { 540 | return mOverlays; 541 | } 542 | 543 | /** 544 | * Create a thumbnail at specified time in a video stream in Bitmap format 545 | * 546 | * @param width width of the thumbnail in pixels 547 | * @param height height of the thumbnail in pixels 548 | * @param timeMs The time in the source video file at which the thumbnail is 549 | * requested (even if trimmed). 550 | * 551 | * @return The thumbnail as a Bitmap. 552 | * 553 | * @throws IOException if a file error occurs 554 | * @throws IllegalArgumentException if time is out of video duration 555 | */ 556 | public abstract Bitmap getThumbnail(int width, int height, long timeMs) 557 | throws IOException; 558 | 559 | /** 560 | * Get the array of Bitmap thumbnails between start and end. 561 | * 562 | * @param width width of the thumbnail in pixels 563 | * @param height height of the thumbnail in pixels 564 | * @param startMs The start of time range in milliseconds 565 | * @param endMs The end of the time range in milliseconds 566 | * @param thumbnailCount The thumbnail count 567 | * @param indices The indices of the thumbnails wanted 568 | * @param callback The callback used to pass back the bitmaps 569 | * 570 | * @throws IOException if a file error occurs 571 | */ 572 | public abstract void getThumbnailList(int width, int height, 573 | long startMs, long endMs, 574 | int thumbnailCount, 575 | int[] indices, 576 | GetThumbnailListCallback callback) 577 | throws IOException; 578 | 579 | public interface GetThumbnailListCallback { 580 | public void onThumbnail(Bitmap bitmap, int index); 581 | } 582 | 583 | // This is for compatibility, only used in tests. 584 | public Bitmap[] getThumbnailList(int width, int height, 585 | long startMs, long endMs, 586 | int thumbnailCount) 587 | throws IOException { 588 | final Bitmap[] bitmaps = new Bitmap[thumbnailCount]; 589 | int[] indices = new int[thumbnailCount]; 590 | for (int i = 0; i < thumbnailCount; i++) { 591 | indices[i] = i; 592 | } 593 | getThumbnailList(width, height, startMs, endMs, 594 | thumbnailCount, indices, new GetThumbnailListCallback() { 595 | public void onThumbnail(Bitmap bitmap, int index) { 596 | bitmaps[index] = bitmap; 597 | } 598 | }); 599 | 600 | return bitmaps; 601 | } 602 | 603 | /* 604 | * {@inheritDoc} 605 | */ 606 | @Override 607 | public boolean equals(Object object) { 608 | if (!(object instanceof MediaItem)) { 609 | return false; 610 | } 611 | return mUniqueId.equals(((MediaItem)object).mUniqueId); 612 | } 613 | 614 | /* 615 | * {@inheritDoc} 616 | */ 617 | @Override 618 | public int hashCode() { 619 | return mUniqueId.hashCode(); 620 | } 621 | 622 | /** 623 | * Invalidate the start and end transitions if necessary 624 | * 625 | * @param startTimeMs The start time of the effect or overlay 626 | * @param durationMs The duration of the effect or overlay 627 | */ 628 | abstract void invalidateTransitions(long startTimeMs, long durationMs); 629 | 630 | /** 631 | * Invalidate the start and end transitions if necessary. This method is 632 | * typically called when the start time and/or duration of an overlay or 633 | * effect is changing. 634 | * 635 | * @param oldStartTimeMs The old start time of the effect or overlay 636 | * @param oldDurationMs The old duration of the effect or overlay 637 | * @param newStartTimeMs The new start time of the effect or overlay 638 | * @param newDurationMs The new duration of the effect or overlay 639 | */ 640 | abstract void invalidateTransitions(long oldStartTimeMs, long oldDurationMs, 641 | long newStartTimeMs, long newDurationMs); 642 | 643 | /** 644 | * Check if two items overlap in time 645 | * 646 | * @param startTimeMs1 Item 1 start time 647 | * @param durationMs1 Item 1 duration 648 | * @param startTimeMs2 Item 2 start time 649 | * @param durationMs2 Item 2 end time 650 | * @return true if the two items overlap 651 | */ 652 | protected boolean isOverlapping(long startTimeMs1, long durationMs1, 653 | long startTimeMs2, long durationMs2) { 654 | if (startTimeMs1 + durationMs1 <= startTimeMs2) { 655 | return false; 656 | } else if (startTimeMs1 >= startTimeMs2 + durationMs2) { 657 | return false; 658 | } 659 | 660 | return true; 661 | } 662 | 663 | /** 664 | * Adjust the duration transitions. 665 | */ 666 | protected void adjustTransitions() { 667 | /** 668 | * Check if the duration of transitions need to be adjusted 669 | */ 670 | if (mBeginTransition != null) { 671 | final long maxDurationMs = mBeginTransition.getMaximumDuration(); 672 | if (mBeginTransition.getDuration() > maxDurationMs) { 673 | mBeginTransition.setDuration(maxDurationMs); 674 | } 675 | } 676 | 677 | if (mEndTransition != null) { 678 | final long maxDurationMs = mEndTransition.getMaximumDuration(); 679 | if (mEndTransition.getDuration() > maxDurationMs) { 680 | mEndTransition.setDuration(maxDurationMs); 681 | } 682 | } 683 | } 684 | 685 | /** 686 | * @return MediaArtistNativeHleper context 687 | */ 688 | MediaArtistNativeHelper getNativeContext() { 689 | return mMANativeHelper; 690 | } 691 | 692 | /** 693 | * Initialises ClipSettings fields to default value 694 | * 695 | * @param ClipSettings object 696 | *{@link android.media.videoeditor.MediaArtistNativeHelper.ClipSettings} 697 | */ 698 | void initClipSettings(ClipSettings clipSettings) { 699 | clipSettings.clipPath = null; 700 | clipSettings.clipDecodedPath = null; 701 | clipSettings.clipOriginalPath = null; 702 | clipSettings.fileType = 0; 703 | clipSettings.endCutTime = 0; 704 | clipSettings.beginCutTime = 0; 705 | clipSettings.beginCutPercent = 0; 706 | clipSettings.endCutPercent = 0; 707 | clipSettings.panZoomEnabled = false; 708 | clipSettings.panZoomPercentStart = 0; 709 | clipSettings.panZoomTopLeftXStart = 0; 710 | clipSettings.panZoomTopLeftYStart = 0; 711 | clipSettings.panZoomPercentEnd = 0; 712 | clipSettings.panZoomTopLeftXEnd = 0; 713 | clipSettings.panZoomTopLeftYEnd = 0; 714 | clipSettings.mediaRendering = 0; 715 | clipSettings.rgbWidth = 0; 716 | clipSettings.rgbHeight = 0; 717 | } 718 | 719 | /** 720 | * @return ClipSettings object with populated data 721 | *{@link android.media.videoeditor.MediaArtistNativeHelper.ClipSettings} 722 | */ 723 | ClipSettings getClipSettings() { 724 | MediaVideoItem mVI = null; 725 | MediaImageItem mII = null; 726 | ClipSettings clipSettings = new ClipSettings(); 727 | initClipSettings(clipSettings); 728 | if (this instanceof MediaVideoItem) { 729 | mVI = (MediaVideoItem)this; 730 | clipSettings.clipPath = mVI.getFilename(); 731 | clipSettings.fileType = mMANativeHelper.getMediaItemFileType(mVI. 732 | getFileType()); 733 | clipSettings.beginCutTime = (int)mVI.getBoundaryBeginTime(); 734 | clipSettings.endCutTime = (int)mVI.getBoundaryEndTime(); 735 | clipSettings.mediaRendering = mMANativeHelper. 736 | getMediaItemRenderingMode(mVI 737 | .getRenderingMode()); 738 | } else if (this instanceof MediaImageItem) { 739 | mII = (MediaImageItem)this; 740 | clipSettings = mII.getImageClipProperties(); 741 | } 742 | return clipSettings; 743 | } 744 | 745 | /** 746 | * Generates a black frame to be used for generating 747 | * begin transition at first media item in storyboard 748 | * or end transition at last media item in storyboard 749 | * 750 | * @param ClipSettings object 751 | *{@link android.media.videoeditor.MediaArtistNativeHelper.ClipSettings} 752 | */ 753 | void generateBlankFrame(ClipSettings clipSettings) { 754 | if (!mBlankFrameGenerated) { 755 | int mWidth = 64; 756 | int mHeight = 64; 757 | mBlankFrameFilename = String.format(mProjectPath + "/" + "ghost.rgb"); 758 | FileOutputStream fl = null; 759 | try { 760 | fl = new FileOutputStream(mBlankFrameFilename); 761 | } catch (IOException e) { 762 | /* catch IO exception */ 763 | } 764 | final DataOutputStream dos = new DataOutputStream(fl); 765 | 766 | final int [] framingBuffer = new int[mWidth]; 767 | 768 | ByteBuffer byteBuffer = ByteBuffer.allocate(framingBuffer.length * 4); 769 | IntBuffer intBuffer; 770 | 771 | byte[] array = byteBuffer.array(); 772 | int tmp = 0; 773 | while(tmp < mHeight) { 774 | intBuffer = byteBuffer.asIntBuffer(); 775 | intBuffer.put(framingBuffer,0,mWidth); 776 | try { 777 | dos.write(array); 778 | } catch (IOException e) { 779 | /* catch file write error */ 780 | } 781 | tmp += 1; 782 | } 783 | 784 | try { 785 | fl.close(); 786 | } catch (IOException e) { 787 | /* file close error */ 788 | } 789 | mBlankFrameGenerated = true; 790 | } 791 | 792 | clipSettings.clipPath = mBlankFrameFilename; 793 | clipSettings.fileType = FileType.JPG; 794 | clipSettings.beginCutTime = 0; 795 | clipSettings.endCutTime = 0; 796 | clipSettings.mediaRendering = MediaRendering.RESIZING; 797 | 798 | clipSettings.rgbWidth = 64; 799 | clipSettings.rgbHeight = 64; 800 | } 801 | 802 | /** 803 | * Invalidates the blank frame generated 804 | */ 805 | void invalidateBlankFrame() { 806 | if (mBlankFrameFilename != null) { 807 | if (new File(mBlankFrameFilename).exists()) { 808 | new File(mBlankFrameFilename).delete(); 809 | mBlankFrameFilename = null; 810 | } 811 | } 812 | } 813 | 814 | } 815 | -------------------------------------------------------------------------------- /src/android/media/videoeditor/MediaProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 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 | 17 | 18 | package android.media.videoeditor; 19 | 20 | import android.media.videoeditor.VideoEditorProfile; 21 | import android.util.Pair; 22 | import java.lang.System; 23 | /** 24 | * This class defines all properties of a media file such as supported height, 25 | * aspect ratio, bitrate for export function. 26 | * {@hide} 27 | */ 28 | public class MediaProperties { 29 | /** 30 | * Supported heights 31 | */ 32 | public static final int HEIGHT_144 = 144; 33 | public static final int HEIGHT_288 = 288; 34 | public static final int HEIGHT_360 = 360; 35 | public static final int HEIGHT_480 = 480; 36 | public static final int HEIGHT_720 = 720; 37 | public static final int HEIGHT_1080 = 1080; 38 | 39 | /** 40 | * Supported aspect ratios 41 | */ 42 | public static final int ASPECT_RATIO_UNDEFINED = 0; 43 | public static final int ASPECT_RATIO_3_2 = 1; 44 | public static final int ASPECT_RATIO_16_9 = 2; 45 | public static final int ASPECT_RATIO_4_3 = 3; 46 | public static final int ASPECT_RATIO_5_3 = 4; 47 | public static final int ASPECT_RATIO_11_9 = 5; 48 | 49 | /** 50 | * The array of supported aspect ratios 51 | */ 52 | private static final int[] ASPECT_RATIOS = new int[] { 53 | ASPECT_RATIO_3_2, 54 | ASPECT_RATIO_16_9, 55 | ASPECT_RATIO_4_3, 56 | ASPECT_RATIO_5_3, 57 | ASPECT_RATIO_11_9 58 | }; 59 | 60 | /** 61 | * Supported resolutions for specific aspect ratios 62 | */ 63 | @SuppressWarnings({"unchecked"}) 64 | private static final Pair[] ASPECT_RATIO_3_2_RESOLUTIONS = 65 | new Pair[] { 66 | new Pair(720, HEIGHT_480), 67 | new Pair(1080, HEIGHT_720) 68 | }; 69 | 70 | @SuppressWarnings({"unchecked"}) 71 | private static final Pair[] ASPECT_RATIO_4_3_RESOLUTIONS = 72 | new Pair[] { 73 | new Pair(640, HEIGHT_480), 74 | new Pair(960, HEIGHT_720) 75 | }; 76 | 77 | @SuppressWarnings({"unchecked"}) 78 | private static final Pair[] ASPECT_RATIO_5_3_RESOLUTIONS = 79 | new Pair[] { 80 | new Pair(800, HEIGHT_480) 81 | }; 82 | 83 | @SuppressWarnings({"unchecked"}) 84 | private static final Pair[] ASPECT_RATIO_11_9_RESOLUTIONS = 85 | new Pair[] { 86 | new Pair(176, HEIGHT_144), 87 | new Pair(352, HEIGHT_288) 88 | }; 89 | 90 | @SuppressWarnings({"unchecked"}) 91 | private static final Pair[] ASPECT_RATIO_16_9_RESOLUTIONS = 92 | new Pair[] { 93 | new Pair(848, HEIGHT_480), 94 | new Pair(1280, HEIGHT_720), 95 | new Pair(1920, HEIGHT_1080), 96 | }; 97 | 98 | /** 99 | * Bitrate values (in bits per second) 100 | */ 101 | public static final int BITRATE_28K = 28000; 102 | public static final int BITRATE_40K = 40000; 103 | public static final int BITRATE_64K = 64000; 104 | public static final int BITRATE_96K = 96000; 105 | public static final int BITRATE_128K = 128000; 106 | public static final int BITRATE_192K = 192000; 107 | public static final int BITRATE_256K = 256000; 108 | public static final int BITRATE_384K = 384000; 109 | public static final int BITRATE_512K = 512000; 110 | public static final int BITRATE_800K = 800000; 111 | public static final int BITRATE_2M = 2000000; 112 | public static final int BITRATE_5M = 5000000; 113 | public static final int BITRATE_8M = 8000000; 114 | 115 | /** 116 | * The array of supported bitrates 117 | */ 118 | private static final int[] SUPPORTED_BITRATES = new int[] { 119 | BITRATE_28K, 120 | BITRATE_40K, 121 | BITRATE_64K, 122 | BITRATE_96K, 123 | BITRATE_128K, 124 | BITRATE_192K, 125 | BITRATE_256K, 126 | BITRATE_384K, 127 | BITRATE_512K, 128 | BITRATE_800K, 129 | BITRATE_2M, 130 | BITRATE_5M, 131 | BITRATE_8M 132 | }; 133 | 134 | /** 135 | * Video codec types 136 | */ 137 | public static final int VCODEC_H263 = 1; 138 | public static final int VCODEC_H264 = 2; 139 | public static final int VCODEC_MPEG4 = 3; 140 | 141 | /** 142 | * The array of supported video codecs 143 | */ 144 | private static final int[] SUPPORTED_VCODECS = new int[] { 145 | VCODEC_H264, 146 | VCODEC_H263, 147 | VCODEC_MPEG4, 148 | }; 149 | 150 | /** 151 | * The H264 profile, the values are same as the one in OMX_Video.h 152 | */ 153 | public final class H264Profile { 154 | public static final int H264ProfileBaseline = 0x01; /**< Baseline profile */ 155 | public static final int H264ProfileMain = 0x02; /**< Main profile */ 156 | public static final int H264ProfileExtended = 0x04; /**< Extended profile */ 157 | public static final int H264ProfileHigh = 0x08; /**< High profile */ 158 | public static final int H264ProfileHigh10 = 0x10; /**< High 10 profile */ 159 | public static final int H264ProfileHigh422 = 0x20; /**< High 4:2:2 profile */ 160 | public static final int H264ProfileHigh444 = 0x40; /**< High 4:4:4 profile */ 161 | public static final int H264ProfileUnknown = 0x7FFFFFFF; 162 | } 163 | /** 164 | * The H264 level, the values are same as the one in OMX_Video.h 165 | */ 166 | public final class H264Level { 167 | public static final int H264Level1 = 0x01; /**< Level 1 */ 168 | public static final int H264Level1b = 0x02; /**< Level 1b */ 169 | public static final int H264Level11 = 0x04; /**< Level 1.1 */ 170 | public static final int H264Level12 = 0x08; /**< Level 1.2 */ 171 | public static final int H264Level13 = 0x10; /**< Level 1.3 */ 172 | public static final int H264Level2 = 0x20; /**< Level 2 */ 173 | public static final int H264Level21 = 0x40; /**< Level 2.1 */ 174 | public static final int H264Level22 = 0x80; /**< Level 2.2 */ 175 | public static final int H264Level3 = 0x100; /**< Level 3 */ 176 | public static final int H264Level31 = 0x200; /**< Level 3.1 */ 177 | public static final int H264Level32 = 0x400; /**< Level 3.2 */ 178 | public static final int H264Level4 = 0x800; /**< Level 4 */ 179 | public static final int H264Level41 = 0x1000; /**< Level 4.1 */ 180 | public static final int H264Level42 = 0x2000; /**< Level 4.2 */ 181 | public static final int H264Level5 = 0x4000; /**< Level 5 */ 182 | public static final int H264Level51 = 0x8000; /**< Level 5.1 */ 183 | public static final int H264LevelUnknown = 0x7FFFFFFF; 184 | } 185 | /** 186 | * The H263 profile, the values are same as the one in OMX_Video.h 187 | */ 188 | public final class H263Profile { 189 | public static final int H263ProfileBaseline = 0x01; 190 | public static final int H263ProfileH320Coding = 0x02; 191 | public static final int H263ProfileBackwardCompatible = 0x04; 192 | public static final int H263ProfileISWV2 = 0x08; 193 | public static final int H263ProfileISWV3 = 0x10; 194 | public static final int H263ProfileHighCompression = 0x20; 195 | public static final int H263ProfileInternet = 0x40; 196 | public static final int H263ProfileInterlace = 0x80; 197 | public static final int H263ProfileHighLatency = 0x100; 198 | public static final int H263ProfileUnknown = 0x7FFFFFFF; 199 | } 200 | /** 201 | * The H263 level, the values are same as the one in OMX_Video.h 202 | */ 203 | public final class H263Level { 204 | public static final int H263Level10 = 0x01; 205 | public static final int H263Level20 = 0x02; 206 | public static final int H263Level30 = 0x04; 207 | public static final int H263Level40 = 0x08; 208 | public static final int H263Level45 = 0x10; 209 | public static final int H263Level50 = 0x20; 210 | public static final int H263Level60 = 0x40; 211 | public static final int H263Level70 = 0x80; 212 | public static final int H263LevelUnknown = 0x7FFFFFFF; 213 | } 214 | /** 215 | * The mpeg4 profile, the values are same as the one in OMX_Video.h 216 | */ 217 | public final class MPEG4Profile { 218 | public static final int MPEG4ProfileSimple = 0x01; 219 | public static final int MPEG4ProfileSimpleScalable = 0x02; 220 | public static final int MPEG4ProfileCore = 0x04; 221 | public static final int MPEG4ProfileMain = 0x08; 222 | public static final int MPEG4ProfileNbit = 0x10; 223 | public static final int MPEG4ProfileScalableTexture = 0x20; 224 | public static final int MPEG4ProfileSimpleFace = 0x40; 225 | public static final int MPEG4ProfileSimpleFBA = 0x80; 226 | public static final int MPEG4ProfileBasicAnimated = 0x100; 227 | public static final int MPEG4ProfileHybrid = 0x200; 228 | public static final int MPEG4ProfileAdvancedRealTime = 0x400; 229 | public static final int MPEG4ProfileCoreScalable = 0x800; 230 | public static final int MPEG4ProfileAdvancedCoding = 0x1000; 231 | public static final int MPEG4ProfileAdvancedCore = 0x2000; 232 | public static final int MPEG4ProfileAdvancedScalable = 0x4000; 233 | public static final int MPEG4ProfileAdvancedSimple = 0x8000; 234 | public static final int MPEG4ProfileUnknown = 0x7FFFFFFF; 235 | } 236 | /** 237 | * The mpeg4 level, the values are same as the one in OMX_Video.h 238 | */ 239 | public final class MPEG4Level { 240 | public static final int MPEG4Level0 = 0x01; /**< Level 0 */ 241 | public static final int MPEG4Level0b = 0x02; /**< Level 0b */ 242 | public static final int MPEG4Level1 = 0x04; /**< Level 1 */ 243 | public static final int MPEG4Level2 = 0x08; /**< Level 2 */ 244 | public static final int MPEG4Level3 = 0x10; /**< Level 3 */ 245 | public static final int MPEG4Level4 = 0x20; /**< Level 4 */ 246 | public static final int MPEG4Level4a = 0x40; /**< Level 4a */ 247 | public static final int MPEG4Level5 = 0x80; /**< Level 5 */ 248 | public static final int MPEG4LevelUnknown = 0x7FFFFFFF; 249 | } 250 | /** 251 | * Audio codec types 252 | */ 253 | public static final int ACODEC_NO_AUDIO = 0; 254 | public static final int ACODEC_AMRNB = 1; 255 | public static final int ACODEC_AAC_LC = 2; 256 | public static final int ACODEC_AAC_PLUS = 3; 257 | public static final int ACODEC_ENHANCED_AAC_PLUS = 4; 258 | public static final int ACODEC_MP3 = 5; 259 | public static final int ACODEC_EVRC = 6; 260 | // 7 value is used for PCM 261 | public static final int ACODEC_AMRWB = 8; 262 | public static final int ACODEC_OGG = 9; 263 | 264 | /** 265 | * The array of supported audio codecs 266 | */ 267 | private static final int[] SUPPORTED_ACODECS = new int[] { 268 | ACODEC_AAC_LC, 269 | ACODEC_AMRNB, 270 | ACODEC_AMRWB 271 | }; 272 | 273 | /** 274 | * Samples per frame for each audio codec 275 | */ 276 | public static final int SAMPLES_PER_FRAME_AAC = 1024; 277 | public static final int SAMPLES_PER_FRAME_MP3 = 1152; 278 | public static final int SAMPLES_PER_FRAME_AMRNB = 160; 279 | public static final int SAMPLES_PER_FRAME_AMRWB = 320; 280 | 281 | public static final int DEFAULT_SAMPLING_FREQUENCY = 32000; 282 | public static final int DEFAULT_CHANNEL_COUNT = 2; 283 | 284 | /** 285 | * File format types 286 | */ 287 | public static final int FILE_3GP = 0; 288 | public static final int FILE_MP4 = 1; 289 | public static final int FILE_AMR = 2; 290 | public static final int FILE_MP3 = 3; 291 | // 4 is for PCM 292 | public static final int FILE_JPEG = 5; 293 | // 6 is for BMP 294 | // 7 is for GIF 295 | public static final int FILE_PNG = 8; 296 | // 9 is for ARGB8888 297 | public static final int FILE_M4V = 10; 298 | public static final int FILE_UNSUPPORTED = 255; 299 | 300 | /** 301 | * Undefined video codec profiles 302 | */ 303 | public static final int UNDEFINED_VIDEO_PROFILE = 255; 304 | 305 | /** 306 | * The array of the supported file formats 307 | */ 308 | private static final int[] SUPPORTED_VIDEO_FILE_FORMATS = new int[] { 309 | FILE_3GP, 310 | FILE_MP4, 311 | FILE_M4V 312 | }; 313 | 314 | /** 315 | * The maximum count of audio tracks supported 316 | */ 317 | public static final int AUDIO_MAX_TRACK_COUNT = 1; 318 | 319 | /** The maximum volume supported (100 means that no amplification is 320 | * supported, i.e. attenuation only) 321 | */ 322 | public static final int AUDIO_MAX_VOLUME_PERCENT = 100; 323 | 324 | /** 325 | * This class cannot be instantiated 326 | */ 327 | private MediaProperties() { 328 | } 329 | 330 | /** 331 | * @return The array of supported aspect ratios 332 | */ 333 | public static int[] getAllSupportedAspectRatios() { 334 | return ASPECT_RATIOS; 335 | } 336 | 337 | /** 338 | * Get the supported resolutions for the specified aspect ratio. 339 | * 340 | * @param aspectRatio The aspect ratio for which the resolutions are 341 | * requested 342 | * @return The array of width and height pairs 343 | */ 344 | public static Pair[] getSupportedResolutions(int aspectRatio) { 345 | final Pair[] resolutions; 346 | switch (aspectRatio) { 347 | case ASPECT_RATIO_3_2: { 348 | resolutions = ASPECT_RATIO_3_2_RESOLUTIONS; 349 | break; 350 | } 351 | 352 | case ASPECT_RATIO_4_3: { 353 | resolutions = ASPECT_RATIO_4_3_RESOLUTIONS; 354 | break; 355 | } 356 | 357 | case ASPECT_RATIO_5_3: { 358 | resolutions = ASPECT_RATIO_5_3_RESOLUTIONS; 359 | break; 360 | } 361 | 362 | case ASPECT_RATIO_11_9: { 363 | resolutions = ASPECT_RATIO_11_9_RESOLUTIONS; 364 | break; 365 | } 366 | 367 | case ASPECT_RATIO_16_9: { 368 | resolutions = ASPECT_RATIO_16_9_RESOLUTIONS; 369 | break; 370 | } 371 | 372 | default: { 373 | throw new IllegalArgumentException("Unknown aspect ratio: " + aspectRatio); 374 | } 375 | } 376 | 377 | /** Check the platform specific maximum export resolution */ 378 | VideoEditorProfile veProfile = VideoEditorProfile.get(); 379 | if (veProfile == null) { 380 | throw new RuntimeException("Can't get the video editor profile"); 381 | } 382 | final int maxWidth = veProfile.maxOutputVideoFrameWidth; 383 | final int maxHeight = veProfile.maxOutputVideoFrameHeight; 384 | Pair[] tmpResolutions = new Pair[resolutions.length]; 385 | int numSupportedResolution = 0; 386 | int i = 0; 387 | 388 | /** Get supported resolution list */ 389 | for (i = 0; i < resolutions.length; i++) { 390 | if ((resolutions[i].first <= maxWidth) && 391 | (resolutions[i].second <= maxHeight)) { 392 | tmpResolutions[numSupportedResolution] = resolutions[i]; 393 | numSupportedResolution++; 394 | } 395 | } 396 | final Pair[] supportedResolutions = 397 | new Pair[numSupportedResolution]; 398 | System.arraycopy(tmpResolutions, 0, 399 | supportedResolutions, 0, numSupportedResolution); 400 | 401 | return supportedResolutions; 402 | } 403 | 404 | /** 405 | * @return The array of supported video codecs 406 | */ 407 | public static int[] getSupportedVideoCodecs() { 408 | return SUPPORTED_VCODECS; 409 | } 410 | 411 | /** 412 | * @return The array of supported audio codecs 413 | */ 414 | public static int[] getSupportedAudioCodecs() { 415 | return SUPPORTED_ACODECS; 416 | } 417 | 418 | /** 419 | * @return The array of supported file formats 420 | */ 421 | public static int[] getSupportedVideoFileFormat() { 422 | return SUPPORTED_VIDEO_FILE_FORMATS; 423 | } 424 | 425 | /** 426 | * @return The array of supported video bitrates 427 | */ 428 | public static int[] getSupportedVideoBitrates() { 429 | return SUPPORTED_BITRATES; 430 | } 431 | 432 | /** 433 | * @return The maximum value for the audio volume 434 | */ 435 | public static int getSupportedMaxVolume() { 436 | return MediaProperties.AUDIO_MAX_VOLUME_PERCENT; 437 | } 438 | 439 | /** 440 | * @return The maximum number of audio tracks supported 441 | */ 442 | public static int getSupportedAudioTrackCount() { 443 | return MediaProperties.AUDIO_MAX_TRACK_COUNT; 444 | } 445 | } 446 | -------------------------------------------------------------------------------- /src/android/media/videoeditor/MediaVideoItem.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 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 | 17 | 18 | package android.media.videoeditor; 19 | 20 | import java.io.File; 21 | import java.io.IOException; 22 | import java.lang.ref.SoftReference; 23 | import android.graphics.Bitmap; 24 | import android.media.videoeditor.MediaArtistNativeHelper.ClipSettings; 25 | import android.media.videoeditor.MediaArtistNativeHelper.Properties; 26 | import android.media.videoeditor.VideoEditorProfile; 27 | import android.view.Surface; 28 | import android.view.SurfaceHolder; 29 | 30 | /** 31 | * This class represents a video clip item on the storyboard 32 | * {@hide} 33 | */ 34 | public class MediaVideoItem extends MediaItem { 35 | 36 | /** 37 | * Instance variables 38 | */ 39 | private final int mWidth; 40 | private final int mHeight; 41 | private final int mAspectRatio; 42 | private final int mFileType; 43 | private final int mVideoType; 44 | private final int mVideoProfile; 45 | private final int mVideoLevel; 46 | private final int mVideoBitrate; 47 | private final long mDurationMs; 48 | private final int mAudioBitrate; 49 | private final int mFps; 50 | private final int mAudioType; 51 | private final int mAudioChannels; 52 | private final int mAudioSamplingFrequency; 53 | private long mBeginBoundaryTimeMs; 54 | private long mEndBoundaryTimeMs; 55 | private int mVolumePercentage; 56 | private boolean mMuted; 57 | private String mAudioWaveformFilename; 58 | private MediaArtistNativeHelper mMANativeHelper; 59 | private VideoEditorImpl mVideoEditor; 60 | private final int mVideoRotationDegree; 61 | /** 62 | * The audio waveform data 63 | */ 64 | private SoftReference mWaveformData; 65 | 66 | /** 67 | * An object of this type cannot be instantiated with a default constructor 68 | */ 69 | @SuppressWarnings("unused") 70 | private MediaVideoItem() throws IOException { 71 | this(null, null, null, RENDERING_MODE_BLACK_BORDER); 72 | } 73 | 74 | /** 75 | * Constructor 76 | * 77 | * @param editor The video editor reference 78 | * @param mediaItemId The MediaItem id 79 | * @param filename The image file name 80 | * @param renderingMode The rendering mode 81 | * 82 | * @throws IOException if the file cannot be opened for reading 83 | */ 84 | public MediaVideoItem(VideoEditor editor, String mediaItemId, String filename, 85 | int renderingMode) throws IOException { 86 | this(editor, mediaItemId, filename, renderingMode, 0, END_OF_FILE, 100, false, null); 87 | } 88 | 89 | /** 90 | * Constructor 91 | * 92 | * @param editor The video editor reference 93 | * @param mediaItemId The MediaItem id 94 | * @param filename The image file name 95 | * @param renderingMode The rendering mode 96 | * @param beginMs Start time in milliseconds. Set to 0 to extract from the 97 | * beginning 98 | * @param endMs End time in milliseconds. Set to {@link #END_OF_FILE} to 99 | * extract until the end 100 | * @param volumePercent in %/. 100% means no change; 50% means half value, 200% 101 | * means double, 0% means silent. 102 | * @param muted true if the audio is muted 103 | * @param audioWaveformFilename The name of the audio waveform file 104 | * 105 | * @throws IOException if the file cannot be opened for reading 106 | */ 107 | MediaVideoItem(VideoEditor editor, String mediaItemId, String filename, 108 | int renderingMode, long beginMs, long endMs, int volumePercent, boolean muted, 109 | String audioWaveformFilename) throws IOException { 110 | super(editor, mediaItemId, filename, renderingMode); 111 | 112 | if (editor instanceof VideoEditorImpl) { 113 | mMANativeHelper = ((VideoEditorImpl)editor).getNativeContext(); 114 | mVideoEditor = ((VideoEditorImpl)editor); 115 | } 116 | 117 | final Properties properties; 118 | try { 119 | properties = mMANativeHelper.getMediaProperties(filename); 120 | } catch ( Exception e) { 121 | throw new IllegalArgumentException(e.getMessage() + " : " + filename); 122 | } 123 | 124 | /** Check the platform specific maximum import resolution */ 125 | VideoEditorProfile veProfile = VideoEditorProfile.get(); 126 | if (veProfile == null) { 127 | throw new RuntimeException("Can't get the video editor profile"); 128 | } 129 | final int maxInputWidth = veProfile.maxInputVideoFrameWidth; 130 | final int maxInputHeight = veProfile.maxInputVideoFrameHeight; 131 | if ((properties.width > maxInputWidth) || 132 | (properties.height > maxInputHeight)) { 133 | throw new IllegalArgumentException( 134 | "Unsupported import resolution. Supported maximum width:" + 135 | maxInputWidth + " height:" + maxInputHeight + 136 | ", current width:" + properties.width + 137 | " height:" + properties.height); 138 | } 139 | /** Check the platform specific maximum video profile and level */ 140 | if (!properties.profileSupported) { 141 | throw new IllegalArgumentException( 142 | "Unsupported video profile " + properties.profile); 143 | } 144 | if (!properties.levelSupported) { 145 | throw new IllegalArgumentException( 146 | "Unsupported video level " + properties.level); 147 | } 148 | switch (mMANativeHelper.getFileType(properties.fileType)) { 149 | case MediaProperties.FILE_3GP: 150 | case MediaProperties.FILE_MP4: 151 | case MediaProperties.FILE_M4V: 152 | break; 153 | 154 | default: 155 | throw new IllegalArgumentException("Unsupported Input File Type"); 156 | } 157 | 158 | switch (mMANativeHelper.getVideoCodecType(properties.videoFormat)) { 159 | case MediaProperties.VCODEC_H263: 160 | case MediaProperties.VCODEC_H264: 161 | case MediaProperties.VCODEC_MPEG4: 162 | break; 163 | 164 | default: 165 | throw new IllegalArgumentException("Unsupported Video Codec Format in Input File"); 166 | } 167 | 168 | mWidth = properties.width; 169 | mHeight = properties.height; 170 | mAspectRatio = mMANativeHelper.getAspectRatio(properties.width, 171 | properties.height); 172 | mFileType = mMANativeHelper.getFileType(properties.fileType); 173 | mVideoType = mMANativeHelper.getVideoCodecType(properties.videoFormat); 174 | mVideoProfile = properties.profile; 175 | mVideoLevel = properties.level; 176 | mDurationMs = properties.videoDuration; 177 | mVideoBitrate = properties.videoBitrate; 178 | mAudioBitrate = properties.audioBitrate; 179 | mFps = (int)properties.averageFrameRate; 180 | mAudioType = mMANativeHelper.getAudioCodecType(properties.audioFormat); 181 | mAudioChannels = properties.audioChannels; 182 | mAudioSamplingFrequency = properties.audioSamplingFrequency; 183 | mBeginBoundaryTimeMs = beginMs; 184 | mEndBoundaryTimeMs = endMs == END_OF_FILE ? mDurationMs : endMs; 185 | mVolumePercentage = volumePercent; 186 | mMuted = muted; 187 | mAudioWaveformFilename = audioWaveformFilename; 188 | if (audioWaveformFilename != null) { 189 | mWaveformData = new SoftReference( 190 | new WaveformData(audioWaveformFilename)); 191 | } else { 192 | mWaveformData = null; 193 | } 194 | mVideoRotationDegree = properties.videoRotation; 195 | } 196 | 197 | /** 198 | * Sets the start and end marks for trimming a video media item. 199 | * This method will adjust the duration of bounding transitions, effects 200 | * and overlays if the current duration of the transactions become greater 201 | * than the maximum allowable duration. 202 | * 203 | * @param beginMs Start time in milliseconds. Set to 0 to extract from the 204 | * beginning 205 | * @param endMs End time in milliseconds. Set to {@link #END_OF_FILE} to 206 | * extract until the end 207 | * 208 | * @throws IllegalArgumentException if the start time is greater or equal than 209 | * end time, the end time is beyond the file duration, the start time 210 | * is negative 211 | */ 212 | public void setExtractBoundaries(long beginMs, long endMs) { 213 | if (beginMs > mDurationMs) { 214 | throw new IllegalArgumentException("setExtractBoundaries: Invalid start time"); 215 | } 216 | 217 | if (endMs > mDurationMs) { 218 | throw new IllegalArgumentException("setExtractBoundaries: Invalid end time"); 219 | } 220 | 221 | if ((endMs != -1) && (beginMs >= endMs) ) { 222 | throw new IllegalArgumentException("setExtractBoundaries: Start time is greater than end time"); 223 | } 224 | 225 | if ((beginMs < 0) || ((endMs != -1) && (endMs < 0))) { 226 | throw new IllegalArgumentException("setExtractBoundaries: Start time or end time is negative"); 227 | } 228 | 229 | mMANativeHelper.setGeneratePreview(true); 230 | 231 | if (beginMs != mBeginBoundaryTimeMs) { 232 | if (mBeginTransition != null) { 233 | mBeginTransition.invalidate(); 234 | } 235 | } 236 | 237 | if (endMs != mEndBoundaryTimeMs) { 238 | if (mEndTransition != null) { 239 | mEndTransition.invalidate(); 240 | } 241 | } 242 | 243 | mBeginBoundaryTimeMs = beginMs; 244 | mEndBoundaryTimeMs = endMs; 245 | adjustTransitions(); 246 | mVideoEditor.updateTimelineDuration(); 247 | /** 248 | * Note that the start and duration of any effects and overlays are 249 | * not adjusted nor are they automatically removed if they fall 250 | * outside the new boundaries. 251 | */ 252 | } 253 | 254 | /** 255 | * @return The boundary begin time 256 | */ 257 | public long getBoundaryBeginTime() { 258 | return mBeginBoundaryTimeMs; 259 | } 260 | 261 | /** 262 | * @return The boundary end time 263 | */ 264 | public long getBoundaryEndTime() { 265 | return mEndBoundaryTimeMs; 266 | } 267 | 268 | /* 269 | * {@inheritDoc} 270 | */ 271 | @Override 272 | public void addEffect(Effect effect) { 273 | if (effect instanceof EffectKenBurns) { 274 | throw new IllegalArgumentException("Ken Burns effects cannot be applied to MediaVideoItem"); 275 | } 276 | super.addEffect(effect); 277 | } 278 | 279 | /* 280 | * {@inheritDoc} 281 | */ 282 | @Override 283 | public Bitmap getThumbnail(int width, int height, long timeMs) { 284 | if (timeMs > mDurationMs) { 285 | throw new IllegalArgumentException("Time Exceeds duration"); 286 | } 287 | 288 | if (timeMs < 0) { 289 | throw new IllegalArgumentException("Invalid Time duration"); 290 | } 291 | 292 | if ((width <= 0) || (height <= 0)) { 293 | throw new IllegalArgumentException("Invalid Dimensions"); 294 | } 295 | 296 | if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270) { 297 | int temp = width; 298 | width = height; 299 | height = temp; 300 | } 301 | 302 | return mMANativeHelper.getPixels( 303 | getFilename(), width, height, timeMs, mVideoRotationDegree); 304 | } 305 | 306 | /* 307 | * {@inheritDoc} 308 | */ 309 | @Override 310 | public void getThumbnailList(int width, int height, 311 | long startMs, long endMs, 312 | int thumbnailCount, 313 | int[] indices, 314 | GetThumbnailListCallback callback) 315 | throws IOException { 316 | if (startMs > endMs) { 317 | throw new IllegalArgumentException("Start time is greater than end time"); 318 | } 319 | 320 | if (endMs > mDurationMs) { 321 | throw new IllegalArgumentException("End time is greater than file duration"); 322 | } 323 | 324 | if ((height <= 0) || (width <= 0)) { 325 | throw new IllegalArgumentException("Invalid dimension"); 326 | } 327 | 328 | if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270) { 329 | int temp = width; 330 | width = height; 331 | height = temp; 332 | } 333 | 334 | mMANativeHelper.getPixelsList(getFilename(), width, height, 335 | startMs, endMs, thumbnailCount, indices, callback, 336 | mVideoRotationDegree); 337 | } 338 | 339 | /* 340 | * {@inheritDoc} 341 | */ 342 | @Override 343 | void invalidateTransitions(long startTimeMs, long durationMs) { 344 | /** 345 | * Check if the item overlaps with the beginning and end transitions 346 | */ 347 | if (mBeginTransition != null) { 348 | if (isOverlapping(startTimeMs, durationMs, 349 | mBeginBoundaryTimeMs, mBeginTransition.getDuration())) { 350 | mBeginTransition.invalidate(); 351 | } 352 | } 353 | 354 | if (mEndTransition != null) { 355 | final long transitionDurationMs = mEndTransition.getDuration(); 356 | if (isOverlapping(startTimeMs, durationMs, 357 | mEndBoundaryTimeMs - transitionDurationMs, transitionDurationMs)) { 358 | mEndTransition.invalidate(); 359 | } 360 | } 361 | } 362 | 363 | /* 364 | * {@inheritDoc} 365 | */ 366 | @Override 367 | void invalidateTransitions(long oldStartTimeMs, long oldDurationMs, long newStartTimeMs, 368 | long newDurationMs) { 369 | /** 370 | * Check if the item overlaps with the beginning and end transitions 371 | */ 372 | if (mBeginTransition != null) { 373 | final long transitionDurationMs = mBeginTransition.getDuration(); 374 | final boolean oldOverlap = isOverlapping(oldStartTimeMs, oldDurationMs, 375 | mBeginBoundaryTimeMs, transitionDurationMs); 376 | final boolean newOverlap = isOverlapping(newStartTimeMs, newDurationMs, 377 | mBeginBoundaryTimeMs, transitionDurationMs); 378 | /** 379 | * Invalidate transition if: 380 | * 381 | * 1. New item overlaps the transition, the old one did not 382 | * 2. New item does not overlap the transition, the old one did 383 | * 3. New and old item overlap the transition if begin or end 384 | * time changed 385 | */ 386 | if (newOverlap != oldOverlap) { // Overlap has changed 387 | mBeginTransition.invalidate(); 388 | } else if (newOverlap) { // Both old and new overlap 389 | if ((oldStartTimeMs != newStartTimeMs) || 390 | !(oldStartTimeMs + oldDurationMs > transitionDurationMs && 391 | newStartTimeMs + newDurationMs > transitionDurationMs)) { 392 | mBeginTransition.invalidate(); 393 | } 394 | } 395 | } 396 | 397 | if (mEndTransition != null) { 398 | final long transitionDurationMs = mEndTransition.getDuration(); 399 | final boolean oldOverlap = isOverlapping(oldStartTimeMs, oldDurationMs, 400 | mEndBoundaryTimeMs - transitionDurationMs, transitionDurationMs); 401 | final boolean newOverlap = isOverlapping(newStartTimeMs, newDurationMs, 402 | mEndBoundaryTimeMs - transitionDurationMs, transitionDurationMs); 403 | /** 404 | * Invalidate transition if: 405 | * 406 | * 1. New item overlaps the transition, the old one did not 407 | * 2. New item does not overlap the transition, the old one did 408 | * 3. New and old item overlap the transition if begin or end 409 | * time changed 410 | */ 411 | if (newOverlap != oldOverlap) { // Overlap has changed 412 | mEndTransition.invalidate(); 413 | } else if (newOverlap) { // Both old and new overlap 414 | if ((oldStartTimeMs + oldDurationMs != newStartTimeMs + newDurationMs) || 415 | ((oldStartTimeMs > mEndBoundaryTimeMs - transitionDurationMs) || 416 | newStartTimeMs > mEndBoundaryTimeMs - transitionDurationMs)) { 417 | mEndTransition.invalidate(); 418 | } 419 | } 420 | } 421 | } 422 | 423 | /* 424 | * {@inheritDoc} 425 | */ 426 | @Override 427 | public int getAspectRatio() { 428 | return mAspectRatio; 429 | } 430 | 431 | /* 432 | * {@inheritDoc} 433 | */ 434 | @Override 435 | public int getFileType() { 436 | return mFileType; 437 | } 438 | 439 | /* 440 | * {@inheritDoc} 441 | */ 442 | @Override 443 | public int getWidth() { 444 | if (mVideoRotationDegree == 90 || 445 | mVideoRotationDegree == 270) { 446 | return mHeight; 447 | } else { 448 | return mWidth; 449 | } 450 | } 451 | 452 | /* 453 | * {@inheritDoc} 454 | */ 455 | @Override 456 | public int getHeight() { 457 | if (mVideoRotationDegree == 90 || 458 | mVideoRotationDegree == 270) { 459 | return mWidth; 460 | } else { 461 | return mHeight; 462 | } 463 | } 464 | 465 | /* 466 | * {@inheritDoc} 467 | */ 468 | @Override 469 | public long getDuration() { 470 | return mDurationMs; 471 | } 472 | 473 | /* 474 | * {@inheritDoc} 475 | */ 476 | @Override 477 | public long getTimelineDuration() { 478 | return mEndBoundaryTimeMs - mBeginBoundaryTimeMs; 479 | } 480 | 481 | /** 482 | * Render a frame according to the playback (in the native aspect ratio) for 483 | * the specified media item. All effects and overlays applied to the media 484 | * item are ignored. The extract boundaries are also ignored. This method 485 | * can be used to playback frames when implementing trimming functionality. 486 | * 487 | * @param surfaceHolder SurfaceHolder used by the application 488 | * @param timeMs time corresponding to the frame to display (relative to the 489 | * the beginning of the media item). 490 | * @return The accurate time stamp of the frame that is rendered . 491 | * @throws IllegalStateException if a playback, preview or an export is 492 | * already in progress 493 | * @throws IllegalArgumentException if time is negative or greater than the 494 | * media item duration 495 | */ 496 | public long renderFrame(SurfaceHolder surfaceHolder, long timeMs) { 497 | if (surfaceHolder == null) { 498 | throw new IllegalArgumentException("Surface Holder is null"); 499 | } 500 | 501 | if (timeMs > mDurationMs || timeMs < 0) { 502 | throw new IllegalArgumentException("requested time not correct"); 503 | } 504 | 505 | final Surface surface = surfaceHolder.getSurface(); 506 | if (surface == null) { 507 | throw new RuntimeException("Surface could not be retrieved from Surface holder"); 508 | } 509 | 510 | if (mFilename != null) { 511 | return mMANativeHelper.renderMediaItemPreviewFrame(surface, 512 | mFilename,timeMs,mWidth,mHeight); 513 | } else { 514 | return 0; 515 | } 516 | } 517 | 518 | 519 | /** 520 | * This API allows to generate a file containing the sample volume levels of 521 | * the Audio track of this media item. This function may take significant 522 | * time and is blocking. The file can be retrieved using 523 | * getAudioWaveformFilename(). 524 | * 525 | * @param listener The progress listener 526 | * 527 | * @throws IOException if the output file cannot be created 528 | * @throws IllegalArgumentException if the mediaItem does not have a valid 529 | * Audio track 530 | */ 531 | public void extractAudioWaveform(ExtractAudioWaveformProgressListener listener) 532 | throws IOException { 533 | int frameDuration = 0; 534 | int sampleCount = 0; 535 | final String projectPath = mMANativeHelper.getProjectPath(); 536 | /** 537 | * Waveform file does not exist 538 | */ 539 | if (mAudioWaveformFilename == null ) { 540 | /** 541 | * Since audioWaveformFilename will not be supplied,it is generated 542 | */ 543 | String mAudioWaveFileName = null; 544 | 545 | mAudioWaveFileName = 546 | String.format(projectPath + "/" + "audioWaveformFile-"+ getId() + ".dat"); 547 | /** 548 | * Logic to get frame duration = (no. of frames per sample * 1000)/ 549 | * sampling frequency 550 | */ 551 | if (mMANativeHelper.getAudioCodecType(mAudioType) == 552 | MediaProperties.ACODEC_AMRNB ) { 553 | frameDuration = (MediaProperties.SAMPLES_PER_FRAME_AMRNB*1000)/ 554 | MediaProperties.DEFAULT_SAMPLING_FREQUENCY; 555 | sampleCount = MediaProperties.SAMPLES_PER_FRAME_AMRNB; 556 | } else if (mMANativeHelper.getAudioCodecType(mAudioType) == 557 | MediaProperties.ACODEC_AMRWB ) { 558 | frameDuration = (MediaProperties.SAMPLES_PER_FRAME_AMRWB * 1000)/ 559 | MediaProperties.DEFAULT_SAMPLING_FREQUENCY; 560 | sampleCount = MediaProperties.SAMPLES_PER_FRAME_AMRWB; 561 | } else if (mMANativeHelper.getAudioCodecType(mAudioType) == 562 | MediaProperties.ACODEC_AAC_LC ) { 563 | frameDuration = (MediaProperties.SAMPLES_PER_FRAME_AAC * 1000)/ 564 | MediaProperties.DEFAULT_SAMPLING_FREQUENCY; 565 | sampleCount = MediaProperties.SAMPLES_PER_FRAME_AAC; 566 | } 567 | 568 | mMANativeHelper.generateAudioGraph( getId(), 569 | mFilename, 570 | mAudioWaveFileName, 571 | frameDuration, 572 | MediaProperties.DEFAULT_CHANNEL_COUNT, 573 | sampleCount, 574 | listener, 575 | true); 576 | /** 577 | * Record the generated file name 578 | */ 579 | mAudioWaveformFilename = mAudioWaveFileName; 580 | } 581 | mWaveformData = 582 | new SoftReference(new WaveformData(mAudioWaveformFilename)); 583 | } 584 | 585 | /** 586 | * Get the audio waveform file name if {@link #extractAudioWaveform()} was 587 | * successful. The file format is as following: 588 | *
    589 | *
  • first 4 bytes provide the number of samples for each value, as big-endian signed
  • 590 | *
  • 4 following bytes is the total number of values in the file, as big-endian signed
  • 591 | *
  • all values follow as bytes Name is unique.
  • 592 | *
593 | * @return the name of the file, null if the file has not been computed or 594 | * if there is no Audio track in the mediaItem 595 | */ 596 | String getAudioWaveformFilename() { 597 | return mAudioWaveformFilename; 598 | } 599 | 600 | /** 601 | * Invalidate the AudioWaveform File 602 | */ 603 | void invalidate() { 604 | if (mAudioWaveformFilename != null) { 605 | new File(mAudioWaveformFilename).delete(); 606 | mAudioWaveformFilename = null; 607 | } 608 | } 609 | 610 | /** 611 | * @return The waveform data 612 | */ 613 | public WaveformData getWaveformData() throws IOException { 614 | if (mWaveformData == null) { 615 | return null; 616 | } 617 | 618 | WaveformData waveformData = mWaveformData.get(); 619 | if (waveformData != null) { 620 | return waveformData; 621 | } else if (mAudioWaveformFilename != null) { 622 | try { 623 | waveformData = new WaveformData(mAudioWaveformFilename); 624 | } catch(IOException e) { 625 | throw e; 626 | } 627 | mWaveformData = new SoftReference(waveformData); 628 | return waveformData; 629 | } else { 630 | return null; 631 | } 632 | } 633 | 634 | /** 635 | * Set volume of the Audio track of this mediaItem 636 | * 637 | * @param volumePercent in %/. 100% means no change; 50% means half value, 200% 638 | * means double, 0% means silent. 639 | * @throws UsupportedOperationException if volume value is not supported 640 | */ 641 | public void setVolume(int volumePercent) { 642 | if ((volumePercent <0) || (volumePercent >100)) { 643 | throw new IllegalArgumentException("Invalid volume"); 644 | } 645 | 646 | mVolumePercentage = volumePercent; 647 | } 648 | 649 | /** 650 | * Get the volume value of the audio track as percentage. Call of this 651 | * method before calling setVolume will always return 100% 652 | * 653 | * @return the volume in percentage 654 | */ 655 | public int getVolume() { 656 | return mVolumePercentage; 657 | } 658 | 659 | /** 660 | * @param muted true to mute the media item 661 | */ 662 | public void setMute(boolean muted) { 663 | mMANativeHelper.setGeneratePreview(true); 664 | mMuted = muted; 665 | if (mBeginTransition != null) { 666 | mBeginTransition.invalidate(); 667 | } 668 | if (mEndTransition != null) { 669 | mEndTransition.invalidate(); 670 | } 671 | } 672 | 673 | /** 674 | * @return true if the media item is muted 675 | */ 676 | public boolean isMuted() { 677 | return mMuted; 678 | } 679 | 680 | /** 681 | * @return The video type 682 | */ 683 | public int getVideoType() { 684 | return mVideoType; 685 | } 686 | 687 | /** 688 | * @return The video profile 689 | */ 690 | public int getVideoProfile() { 691 | return mVideoProfile; 692 | } 693 | 694 | /** 695 | * @return The video profile 696 | */ 697 | public int getVideoLevel() { 698 | return mVideoLevel; 699 | } 700 | 701 | /** 702 | * @return The video bitrate 703 | */ 704 | public int getVideoBitrate() { 705 | return mVideoBitrate; 706 | } 707 | 708 | /** 709 | * @return The audio bitrate 710 | */ 711 | public int getAudioBitrate() { 712 | return mAudioBitrate; 713 | } 714 | 715 | /** 716 | * @return The number of frames per second 717 | */ 718 | public int getFps() { 719 | return mFps; 720 | } 721 | 722 | /** 723 | * @return The audio codec 724 | */ 725 | public int getAudioType() { 726 | return mAudioType; 727 | } 728 | 729 | /** 730 | * @return The number of audio channels 731 | */ 732 | public int getAudioChannels() { 733 | return mAudioChannels; 734 | } 735 | 736 | /** 737 | * @return The audio sample frequency 738 | */ 739 | public int getAudioSamplingFrequency() { 740 | return mAudioSamplingFrequency; 741 | } 742 | 743 | /** 744 | * @return The Video media item properties in ClipSettings class object 745 | * {@link android.media.videoeditor.MediaArtistNativeHelper.ClipSettings} 746 | */ 747 | ClipSettings getVideoClipProperties() { 748 | ClipSettings clipSettings = new ClipSettings(); 749 | clipSettings.clipPath = getFilename(); 750 | clipSettings.fileType = mMANativeHelper.getMediaItemFileType(getFileType()); 751 | clipSettings.beginCutTime = (int)getBoundaryBeginTime(); 752 | clipSettings.endCutTime = (int)getBoundaryEndTime(); 753 | clipSettings.mediaRendering = mMANativeHelper.getMediaItemRenderingMode(getRenderingMode()); 754 | clipSettings.rotationDegree = mVideoRotationDegree; 755 | 756 | return clipSettings; 757 | } 758 | } 759 | -------------------------------------------------------------------------------- /src/android/media/videoeditor/Overlay.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 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 | 17 | 18 | package android.media.videoeditor; 19 | 20 | import java.util.HashMap; 21 | import java.util.Map; 22 | 23 | /** 24 | * This is the super class for all Overlay classes. 25 | * {@hide} 26 | */ 27 | public abstract class Overlay { 28 | /** 29 | * Instance variables 30 | */ 31 | private final String mUniqueId; 32 | /** 33 | * The overlay owner 34 | */ 35 | private final MediaItem mMediaItem; 36 | /** 37 | * user attributes 38 | */ 39 | private final Map mUserAttributes; 40 | 41 | protected long mStartTimeMs; 42 | protected long mDurationMs; 43 | 44 | /** 45 | * Default constructor 46 | */ 47 | @SuppressWarnings("unused") 48 | private Overlay() { 49 | this(null, null, 0, 0); 50 | } 51 | 52 | /** 53 | * Constructor 54 | * 55 | * @param mediaItem The media item owner 56 | * @param overlayId The overlay id 57 | * @param startTimeMs The start time relative to the media item start time 58 | * @param durationMs The duration 59 | * 60 | * @throws IllegalArgumentException if the file type is not PNG or the 61 | * startTimeMs and durationMs are incorrect. 62 | */ 63 | public Overlay(MediaItem mediaItem, String overlayId, long startTimeMs, 64 | long durationMs) { 65 | if (mediaItem == null) { 66 | throw new IllegalArgumentException("Media item cannot be null"); 67 | } 68 | 69 | if ((startTimeMs<0) || (durationMs<0) ) { 70 | throw new IllegalArgumentException("Invalid start time and/OR duration"); 71 | } 72 | 73 | if (startTimeMs + durationMs > mediaItem.getDuration()) { 74 | throw new IllegalArgumentException("Invalid start time and duration"); 75 | } 76 | 77 | mMediaItem = mediaItem; 78 | mUniqueId = overlayId; 79 | mStartTimeMs = startTimeMs; 80 | mDurationMs = durationMs; 81 | mUserAttributes = new HashMap(); 82 | } 83 | 84 | /** 85 | * Get the overlay ID. 86 | * 87 | * @return The of the overlay 88 | */ 89 | public String getId() { 90 | return mUniqueId; 91 | } 92 | 93 | /** 94 | * Get the duration of overlay. 95 | * 96 | * @return The duration of the overlay effect 97 | */ 98 | public long getDuration() { 99 | return mDurationMs; 100 | } 101 | 102 | /** 103 | * If a preview or export is in progress, then this change is effective for 104 | * next preview or export session. 105 | * 106 | * @param durationMs The duration in milliseconds 107 | */ 108 | public void setDuration(long durationMs) { 109 | if (durationMs < 0) { 110 | throw new IllegalArgumentException("Invalid duration"); 111 | } 112 | 113 | if (mStartTimeMs + durationMs > mMediaItem.getDuration()) { 114 | throw new IllegalArgumentException("Duration is too large"); 115 | } 116 | 117 | getMediaItem().getNativeContext().setGeneratePreview(true); 118 | 119 | final long oldDurationMs = mDurationMs; 120 | mDurationMs = durationMs; 121 | 122 | mMediaItem.invalidateTransitions(mStartTimeMs, oldDurationMs, mStartTimeMs, mDurationMs); 123 | } 124 | 125 | /** 126 | * Get the start time of overlay. 127 | * 128 | * @return the start time of the overlay 129 | */ 130 | public long getStartTime() { 131 | return mStartTimeMs; 132 | } 133 | 134 | /** 135 | * Set the start time for the overlay. If a preview or export is in 136 | * progress, then this change is effective for next preview or export 137 | * session. 138 | * 139 | * @param startTimeMs start time in milliseconds 140 | */ 141 | public void setStartTime(long startTimeMs) { 142 | if (startTimeMs + mDurationMs > mMediaItem.getDuration()) { 143 | throw new IllegalArgumentException("Start time is too large"); 144 | } 145 | 146 | getMediaItem().getNativeContext().setGeneratePreview(true); 147 | 148 | final long oldStartTimeMs = mStartTimeMs; 149 | mStartTimeMs = startTimeMs; 150 | 151 | mMediaItem.invalidateTransitions(oldStartTimeMs, mDurationMs, mStartTimeMs, mDurationMs); 152 | } 153 | 154 | /** 155 | * Set the start time and duration 156 | * 157 | * @param startTimeMs start time in milliseconds 158 | * @param durationMs The duration in milliseconds 159 | */ 160 | public void setStartTimeAndDuration(long startTimeMs, long durationMs) { 161 | if (startTimeMs + durationMs > mMediaItem.getDuration()) { 162 | throw new IllegalArgumentException("Invalid start time or duration"); 163 | } 164 | 165 | getMediaItem().getNativeContext().setGeneratePreview(true); 166 | 167 | final long oldStartTimeMs = mStartTimeMs; 168 | final long oldDurationMs = mDurationMs; 169 | 170 | mStartTimeMs = startTimeMs; 171 | mDurationMs = durationMs; 172 | 173 | mMediaItem.invalidateTransitions(oldStartTimeMs, oldDurationMs, mStartTimeMs, mDurationMs); 174 | } 175 | 176 | /** 177 | * Get the media item owner. 178 | * 179 | * @return The media item owner. 180 | */ 181 | public MediaItem getMediaItem() { 182 | return mMediaItem; 183 | } 184 | 185 | /** 186 | * Set a user attribute 187 | * 188 | * @param name The attribute name 189 | * @param value The attribute value 190 | */ 191 | public void setUserAttribute(String name, String value) { 192 | mUserAttributes.put(name, value); 193 | } 194 | 195 | /** 196 | * Get the current user attributes set. 197 | * 198 | * @return The user attributes 199 | */ 200 | public Map getUserAttributes() { 201 | return mUserAttributes; 202 | } 203 | 204 | /* 205 | * {@inheritDoc} 206 | */ 207 | @Override 208 | public boolean equals(Object object) { 209 | if (!(object instanceof Overlay)) { 210 | return false; 211 | } 212 | return mUniqueId.equals(((Overlay)object).mUniqueId); 213 | } 214 | 215 | /* 216 | * {@inheritDoc} 217 | */ 218 | @Override 219 | public int hashCode() { 220 | return mUniqueId.hashCode(); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/android/media/videoeditor/OverlayFrame.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 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 | 17 | 18 | package android.media.videoeditor; 19 | 20 | import java.io.File; 21 | import java.io.FileNotFoundException; 22 | import java.io.FileOutputStream; 23 | import java.io.IOException; 24 | import java.io.DataOutputStream; 25 | import java.nio.ByteBuffer; 26 | import java.nio.IntBuffer; 27 | 28 | import android.graphics.Canvas; 29 | import android.graphics.Paint; 30 | import android.graphics.Rect; 31 | import android.graphics.Bitmap; 32 | import android.graphics.BitmapFactory; 33 | import android.graphics.Bitmap.CompressFormat; 34 | import android.util.Pair; 35 | 36 | 37 | /** 38 | * This class is used to overlay an image on top of a media item. 39 | * {@hide} 40 | */ 41 | public class OverlayFrame extends Overlay { 42 | /** 43 | * Instance variables 44 | */ 45 | private Bitmap mBitmap; 46 | private String mFilename; 47 | private String mBitmapFileName; 48 | 49 | private int mOFWidth; 50 | private int mOFHeight; 51 | 52 | /** 53 | * resized RGB Image dimensions 54 | */ 55 | private int mResizedRGBWidth; 56 | private int mResizedRGBHeight; 57 | 58 | /** 59 | * The resize paint 60 | */ 61 | private static final Paint sResizePaint = new Paint(Paint.FILTER_BITMAP_FLAG); 62 | 63 | /** 64 | * An object of this type cannot be instantiated by using the default 65 | * constructor 66 | */ 67 | @SuppressWarnings("unused") 68 | private OverlayFrame() { 69 | this(null, null, (String)null, 0, 0); 70 | } 71 | 72 | /** 73 | * Constructor for an OverlayFrame 74 | * 75 | * @param mediaItem The media item owner 76 | * @param overlayId The overlay id 77 | * @param bitmap The bitmap to be used as an overlay. The size of the 78 | * bitmap must equal to the size of the media item to which it is 79 | * added. The bitmap is typically a decoded PNG file. 80 | * @param startTimeMs The overlay start time in milliseconds 81 | * @param durationMs The overlay duration in milliseconds 82 | * 83 | * @throws IllegalArgumentException if the file type is not PNG or the 84 | * startTimeMs and durationMs are incorrect. 85 | */ 86 | public OverlayFrame(MediaItem mediaItem, String overlayId, Bitmap bitmap, 87 | long startTimeMs,long durationMs) { 88 | super(mediaItem, overlayId, startTimeMs, durationMs); 89 | mBitmap = bitmap; 90 | mFilename = null; 91 | mBitmapFileName = null; 92 | mResizedRGBWidth = 0; 93 | mResizedRGBHeight = 0; 94 | } 95 | 96 | /** 97 | * Constructor for an OverlayFrame. This constructor can be used to 98 | * restore the overlay after it was saved internally by the video editor. 99 | * 100 | * @param mediaItem The media item owner 101 | * @param overlayId The overlay id 102 | * @param filename The file name that contains the overlay. 103 | * @param startTimeMs The overlay start time in milliseconds 104 | * @param durationMs The overlay duration in milliseconds 105 | * 106 | * @throws IllegalArgumentException if the file type is not PNG or the 107 | * startTimeMs and durationMs are incorrect. 108 | */ 109 | OverlayFrame(MediaItem mediaItem, String overlayId, String filename, 110 | long startTimeMs,long durationMs) { 111 | super(mediaItem, overlayId, startTimeMs, durationMs); 112 | mBitmapFileName = filename; 113 | mBitmap = BitmapFactory.decodeFile(mBitmapFileName); 114 | mFilename = null; 115 | mResizedRGBWidth = 0; 116 | mResizedRGBHeight = 0; 117 | } 118 | 119 | /** 120 | * Get the overlay bitmap. 121 | * 122 | * @return Get the overlay bitmap 123 | */ 124 | public Bitmap getBitmap() { 125 | return mBitmap; 126 | } 127 | 128 | /** 129 | * Get the overlay bitmap. 130 | * 131 | * @return Get the overlay bitmap as png file. 132 | */ 133 | String getBitmapImageFileName() { 134 | return mBitmapFileName; 135 | } 136 | /** 137 | * Set the overlay bitmap. 138 | * 139 | * @param bitmap The overlay bitmap. 140 | */ 141 | public void setBitmap(Bitmap bitmap) { 142 | getMediaItem().getNativeContext().setGeneratePreview(true); 143 | 144 | invalidate(); 145 | 146 | mBitmap = bitmap; 147 | if (mFilename != null) { 148 | /** 149 | * Delete the file 150 | */ 151 | new File(mFilename).delete(); 152 | /** 153 | * Invalidate the filename 154 | */ 155 | mFilename = null; 156 | } 157 | 158 | /** 159 | * Invalidate the transitions if necessary 160 | */ 161 | getMediaItem().invalidateTransitions(mStartTimeMs, mDurationMs); 162 | } 163 | 164 | /** 165 | * Get the file name of this overlay 166 | */ 167 | String getFilename() { 168 | return mFilename; 169 | } 170 | 171 | /* 172 | * Set the file name of this overlay 173 | */ 174 | void setFilename(String filename) { 175 | mFilename = filename; 176 | } 177 | /** 178 | * Save the overlay to the project folder 179 | * 180 | * @param path The path where the overlay will be saved 181 | * 182 | * @return The filename 183 | * @throws FileNotFoundException if the bitmap cannot be saved 184 | * @throws IOException if the bitmap file cannot be saved 185 | */ 186 | String save(String path) throws FileNotFoundException, IOException { 187 | if (mFilename != null) { 188 | return mFilename; 189 | } 190 | 191 | // Create the compressed PNG file 192 | mBitmapFileName = path + "/" + "Overlay" + getId() + ".png"; 193 | if (!(new File(mBitmapFileName).exists())) { 194 | final FileOutputStream out = new FileOutputStream (mBitmapFileName); 195 | mBitmap.compress(CompressFormat.PNG, 100, out); 196 | out.flush(); 197 | out.close(); 198 | } 199 | 200 | mOFWidth = mBitmap.getWidth(); 201 | mOFHeight = mBitmap.getHeight(); 202 | 203 | mFilename = path + "/" + "Overlay" + getId() + ".rgb"; 204 | 205 | /* resize and save rgb as per project aspect ratio */ 206 | MediaArtistNativeHelper nativeHelper = (super.getMediaItem()).getNativeContext(); 207 | 208 | /* get height and width for story board aspect ratio */ 209 | final Pair maxResolution; 210 | final Pair[] resolutions; 211 | resolutions = MediaProperties.getSupportedResolutions(nativeHelper.nativeHelperGetAspectRatio()); 212 | 213 | // Get the highest resolution 214 | maxResolution = resolutions[resolutions.length - 1]; 215 | 216 | /* Generate the rgb file with rendering mode */ 217 | generateOverlayWithRenderingMode (super.getMediaItem(), this, 218 | maxResolution.second /* max Height */ , 219 | maxResolution.first /* max Width */); 220 | 221 | return mFilename; 222 | } 223 | 224 | /** 225 | * Get the OverlayFrame Height 226 | */ 227 | int getOverlayFrameHeight() { 228 | return mOFHeight; 229 | } 230 | 231 | /** 232 | * Get the OverlayFrame Width 233 | */ 234 | int getOverlayFrameWidth() { 235 | return mOFWidth; 236 | } 237 | 238 | /* 239 | * Set the OverlayFrame Height 240 | */ 241 | void setOverlayFrameHeight(int height) { 242 | mOFHeight = height; 243 | } 244 | 245 | /* 246 | * Set the OverlayFrame Width 247 | */ 248 | void setOverlayFrameWidth(int width) { 249 | mOFWidth = width; 250 | } 251 | 252 | /* 253 | * Set the resized RGB widht and height 254 | */ 255 | void setResizedRGBSize(int width, int height) { 256 | mResizedRGBWidth = width; 257 | mResizedRGBHeight = height; 258 | } 259 | 260 | /* 261 | * Get the resized RGB Height 262 | */ 263 | int getResizedRGBSizeHeight() { 264 | return mResizedRGBHeight; 265 | } 266 | 267 | /* 268 | * Get the resized RGB Width 269 | */ 270 | int getResizedRGBSizeWidth() { 271 | return mResizedRGBWidth; 272 | } 273 | 274 | 275 | /** 276 | * Delete the overlay files 277 | */ 278 | void invalidate() { 279 | if (mBitmap != null) { 280 | mBitmap.recycle(); 281 | mBitmap = null; 282 | } 283 | 284 | if (mFilename != null) { 285 | new File(mFilename).delete(); 286 | mFilename = null; 287 | } 288 | 289 | if (mBitmapFileName != null) { 290 | new File(mBitmapFileName).delete(); 291 | mBitmapFileName = null; 292 | } 293 | } 294 | 295 | /** 296 | * Delete the overlay related files 297 | */ 298 | void invalidateGeneratedFiles() { 299 | if (mFilename != null) { 300 | new File(mFilename).delete(); 301 | mFilename = null; 302 | } 303 | 304 | if (mBitmapFileName != null) { 305 | new File(mBitmapFileName).delete(); 306 | mBitmapFileName = null; 307 | } 308 | } 309 | 310 | void generateOverlayWithRenderingMode (MediaItem mediaItemsList, OverlayFrame overlay, int height , int width) 311 | throws FileNotFoundException, IOException { 312 | 313 | final MediaItem t = mediaItemsList; 314 | 315 | /* get the rendering mode */ 316 | int renderMode = t.getRenderingMode(); 317 | 318 | Bitmap overlayBitmap = ((OverlayFrame)overlay).getBitmap(); 319 | 320 | /* 321 | * Check if the resize of Overlay is needed with rendering mode applied 322 | * because of change in export dimensions 323 | */ 324 | int resizedRGBFileHeight = ((OverlayFrame)overlay).getResizedRGBSizeHeight(); 325 | int resizedRGBFileWidth = ((OverlayFrame)overlay).getResizedRGBSizeWidth(); 326 | 327 | /* Get original bitmap width if it is not resized */ 328 | if(resizedRGBFileWidth == 0) { 329 | resizedRGBFileWidth = overlayBitmap.getWidth(); 330 | } 331 | /* Get original bitmap height if it is not resized */ 332 | if(resizedRGBFileHeight == 0) { 333 | resizedRGBFileHeight = overlayBitmap.getHeight(); 334 | } 335 | 336 | if (resizedRGBFileWidth != width || resizedRGBFileHeight != height 337 | || (!(new File(((OverlayFrame)overlay).getFilename()).exists()))) { 338 | /* 339 | * Create the canvas bitmap 340 | */ 341 | final Bitmap destBitmap = Bitmap.createBitmap((int)width, 342 | (int)height, 343 | Bitmap.Config.ARGB_8888); 344 | final Canvas overlayCanvas = new Canvas(destBitmap); 345 | final Rect destRect; 346 | final Rect srcRect; 347 | 348 | switch (renderMode) { 349 | case MediaItem.RENDERING_MODE_STRETCH: { 350 | destRect = new Rect(0, 0, overlayCanvas.getWidth(), 351 | overlayCanvas.getHeight()); 352 | srcRect = new Rect(0, 0, overlayBitmap.getWidth(), 353 | overlayBitmap.getHeight()); 354 | break; 355 | } 356 | 357 | case MediaItem.RENDERING_MODE_BLACK_BORDER: { 358 | int left, right, top, bottom; 359 | float aROverlayImage, aRCanvas; 360 | aROverlayImage = (float)(overlayBitmap.getWidth()) / 361 | (float)(overlayBitmap.getHeight()); 362 | 363 | aRCanvas = (float)(overlayCanvas.getWidth()) / 364 | (float)(overlayCanvas.getHeight()); 365 | 366 | if (aROverlayImage > aRCanvas) { 367 | int newHeight = ((overlayCanvas.getWidth() * overlayBitmap.getHeight()) 368 | / overlayBitmap.getWidth()); 369 | left = 0; 370 | top = (overlayCanvas.getHeight() - newHeight) / 2; 371 | right = overlayCanvas.getWidth(); 372 | bottom = top + newHeight; 373 | } else { 374 | int newWidth = ((overlayCanvas.getHeight() * overlayBitmap.getWidth()) 375 | / overlayBitmap.getHeight()); 376 | left = (overlayCanvas.getWidth() - newWidth) / 2; 377 | top = 0; 378 | right = left + newWidth; 379 | bottom = overlayCanvas.getHeight(); 380 | } 381 | 382 | destRect = new Rect(left, top, right, bottom); 383 | srcRect = new Rect(0, 0, overlayBitmap.getWidth(), overlayBitmap.getHeight()); 384 | break; 385 | } 386 | 387 | case MediaItem.RENDERING_MODE_CROPPING: { 388 | // Calculate the source rect 389 | int left, right, top, bottom; 390 | float aROverlayImage, aRCanvas; 391 | aROverlayImage = (float)(overlayBitmap.getWidth()) / 392 | (float)(overlayBitmap.getHeight()); 393 | aRCanvas = (float)(overlayCanvas.getWidth()) / 394 | (float)(overlayCanvas.getHeight()); 395 | if (aROverlayImage < aRCanvas) { 396 | int newHeight = ((overlayBitmap.getWidth() * overlayCanvas.getHeight()) 397 | / overlayCanvas.getWidth()); 398 | 399 | left = 0; 400 | top = (overlayBitmap.getHeight() - newHeight) / 2; 401 | right = overlayBitmap.getWidth(); 402 | bottom = top + newHeight; 403 | } else { 404 | int newWidth = ((overlayBitmap.getHeight() * overlayCanvas.getWidth()) 405 | / overlayCanvas.getHeight()); 406 | left = (overlayBitmap.getWidth() - newWidth) / 2; 407 | top = 0; 408 | right = left + newWidth; 409 | bottom = overlayBitmap.getHeight(); 410 | } 411 | 412 | srcRect = new Rect(left, top, right, bottom); 413 | destRect = new Rect(0, 0, overlayCanvas.getWidth(), overlayCanvas.getHeight()); 414 | break; 415 | } 416 | 417 | default: { 418 | throw new IllegalStateException("Rendering mode: " + renderMode); 419 | } 420 | } 421 | 422 | overlayCanvas.drawBitmap(overlayBitmap, srcRect, destRect, sResizePaint); 423 | overlayCanvas.setBitmap(null); 424 | 425 | /* 426 | * Write to the dest file 427 | */ 428 | String outFileName = ((OverlayFrame)overlay).getFilename(); 429 | 430 | /* 431 | * Save the image to same rgb file 432 | */ 433 | if (outFileName != null) { 434 | new File(outFileName).delete(); 435 | } 436 | 437 | final FileOutputStream fl = new FileOutputStream(outFileName); 438 | final DataOutputStream dos = new DataOutputStream(fl); 439 | 440 | /* 441 | * Populate the rgb file with bitmap data 442 | */ 443 | final int [] framingBuffer = new int[width]; 444 | ByteBuffer byteBuffer = ByteBuffer.allocate(framingBuffer.length * 4); 445 | IntBuffer intBuffer; 446 | 447 | byte[] array = byteBuffer.array(); 448 | int tmp = 0; 449 | while(tmp < height) { 450 | destBitmap.getPixels(framingBuffer,0,width,0,tmp,width,1); 451 | intBuffer = byteBuffer.asIntBuffer(); 452 | intBuffer.put(framingBuffer,0,width); 453 | dos.write(array); 454 | tmp += 1; 455 | } 456 | fl.flush(); 457 | fl.close(); 458 | 459 | /* 460 | * Set the resized RGB width and height 461 | */ 462 | ((OverlayFrame)overlay).setResizedRGBSize(width, height); 463 | } 464 | } 465 | } 466 | -------------------------------------------------------------------------------- /src/android/media/videoeditor/Transition.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 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 | 17 | 18 | package android.media.videoeditor; 19 | 20 | import java.io.File; 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | 24 | import android.media.videoeditor.MediaArtistNativeHelper.AlphaMagicSettings; 25 | import android.media.videoeditor.MediaArtistNativeHelper.AudioTransition; 26 | import android.media.videoeditor.MediaArtistNativeHelper.ClipSettings; 27 | import android.media.videoeditor.MediaArtistNativeHelper.EditSettings; 28 | import android.media.videoeditor.MediaArtistNativeHelper.EffectSettings; 29 | import android.media.videoeditor.MediaArtistNativeHelper.SlideTransitionSettings; 30 | import android.media.videoeditor.MediaArtistNativeHelper.TransitionSettings; 31 | import android.media.videoeditor.MediaArtistNativeHelper.VideoTransition; 32 | 33 | /** 34 | * This class is super class for all transitions. Transitions (with the 35 | * exception of TransitionAtStart and TransitioAtEnd) can only be inserted 36 | * between media items. 37 | * 38 | * Adding a transition between MediaItems makes the 39 | * duration of the storyboard shorter by the duration of the Transition itself. 40 | * As a result, if the duration of the transition is larger than the smaller 41 | * duration of the two MediaItems associated with the Transition, an exception 42 | * will be thrown. 43 | * 44 | * During a transition, the audio track are cross-fading 45 | * automatically. {@hide} 46 | */ 47 | public abstract class Transition { 48 | /** 49 | * The transition behavior 50 | */ 51 | private static final int BEHAVIOR_MIN_VALUE = 0; 52 | 53 | /** The transition starts slowly and speed up */ 54 | public static final int BEHAVIOR_SPEED_UP = 0; 55 | /** The transition start fast and speed down */ 56 | public static final int BEHAVIOR_SPEED_DOWN = 1; 57 | /** The transition speed is constant */ 58 | public static final int BEHAVIOR_LINEAR = 2; 59 | /** The transition starts fast and ends fast with a slow middle */ 60 | public static final int BEHAVIOR_MIDDLE_SLOW = 3; 61 | /** The transition starts slowly and ends slowly with a fast middle */ 62 | public static final int BEHAVIOR_MIDDLE_FAST = 4; 63 | 64 | private static final int BEHAVIOR_MAX_VALUE = 4; 65 | 66 | /** 67 | * The unique id of the transition 68 | */ 69 | private final String mUniqueId; 70 | 71 | /** 72 | * The transition is applied at the end of this media item 73 | */ 74 | private final MediaItem mAfterMediaItem; 75 | /** 76 | * The transition is applied at the beginning of this media item 77 | */ 78 | private final MediaItem mBeforeMediaItem; 79 | 80 | /** 81 | * The transition behavior 82 | */ 83 | protected final int mBehavior; 84 | 85 | /** 86 | * The transition duration 87 | */ 88 | protected long mDurationMs; 89 | 90 | /** 91 | * The transition filename 92 | */ 93 | protected String mFilename; 94 | 95 | protected MediaArtistNativeHelper mNativeHelper; 96 | /** 97 | * An object of this type cannot be instantiated by using the default 98 | * constructor 99 | */ 100 | @SuppressWarnings("unused") 101 | private Transition() { 102 | this(null, null, null, 0, 0); 103 | } 104 | 105 | /** 106 | * Constructor 107 | * 108 | * @param transitionId The transition id 109 | * @param afterMediaItem The transition is applied to the end of this 110 | * media item 111 | * @param beforeMediaItem The transition is applied to the beginning of 112 | * this media item 113 | * @param durationMs The duration of the transition in milliseconds 114 | * @param behavior The transition behavior 115 | */ 116 | protected Transition(String transitionId, MediaItem afterMediaItem, 117 | MediaItem beforeMediaItem,long durationMs, 118 | int behavior) { 119 | if (behavior < BEHAVIOR_MIN_VALUE || behavior > BEHAVIOR_MAX_VALUE) { 120 | throw new IllegalArgumentException("Invalid behavior: " + behavior); 121 | } 122 | if ((afterMediaItem == null) && (beforeMediaItem == null)) { 123 | throw new IllegalArgumentException("Null media items"); 124 | } 125 | mUniqueId = transitionId; 126 | mAfterMediaItem = afterMediaItem; 127 | mBeforeMediaItem = beforeMediaItem; 128 | mDurationMs = durationMs; 129 | mBehavior = behavior; 130 | mNativeHelper = null; 131 | if (durationMs > getMaximumDuration()) { 132 | throw new IllegalArgumentException("The duration is too large"); 133 | } 134 | if (afterMediaItem != null) { 135 | mNativeHelper = afterMediaItem.getNativeContext(); 136 | }else { 137 | mNativeHelper = beforeMediaItem.getNativeContext(); 138 | } 139 | } 140 | 141 | /** 142 | * Get the ID of the transition. 143 | * 144 | * @return The ID of the transition 145 | */ 146 | public String getId() { 147 | return mUniqueId; 148 | } 149 | 150 | /** 151 | * Get the media item at the end of which the transition is applied. 152 | * 153 | * @return The media item at the end of which the transition is applied 154 | */ 155 | public MediaItem getAfterMediaItem() { 156 | return mAfterMediaItem; 157 | } 158 | 159 | /** 160 | * Get the media item at the beginning of which the transition is applied. 161 | * 162 | * @return The media item at the beginning of which the transition is 163 | * applied 164 | */ 165 | public MediaItem getBeforeMediaItem() { 166 | return mBeforeMediaItem; 167 | } 168 | 169 | /** 170 | * Set the duration of the transition. 171 | * 172 | * @param durationMs the duration of the transition in milliseconds 173 | */ 174 | public void setDuration(long durationMs) { 175 | if (durationMs > getMaximumDuration()) { 176 | throw new IllegalArgumentException("The duration is too large"); 177 | } 178 | 179 | mDurationMs = durationMs; 180 | invalidate(); 181 | mNativeHelper.setGeneratePreview(true); 182 | } 183 | 184 | /** 185 | * Get the duration of the transition. 186 | * 187 | * @return the duration of the transition in milliseconds 188 | */ 189 | public long getDuration() { 190 | return mDurationMs; 191 | } 192 | 193 | /** 194 | * The duration of a transition cannot be greater than half of the minimum 195 | * duration of the bounding media items. 196 | * 197 | * @return The maximum duration of this transition 198 | */ 199 | public long getMaximumDuration() { 200 | if (mAfterMediaItem == null) { 201 | return mBeforeMediaItem.getTimelineDuration() / 2; 202 | } else if (mBeforeMediaItem == null) { 203 | return mAfterMediaItem.getTimelineDuration() / 2; 204 | } else { 205 | return (Math.min(mAfterMediaItem.getTimelineDuration(), 206 | mBeforeMediaItem.getTimelineDuration()) / 2); 207 | } 208 | } 209 | 210 | /** 211 | * Get the behavior of the transition. 212 | * 213 | * @return The behavior 214 | */ 215 | public int getBehavior() { 216 | return mBehavior; 217 | } 218 | 219 | /** 220 | * Get the transition data. 221 | * 222 | * @return The transition data in TransitionSettings object 223 | * {@link android.media.videoeditor.MediaArtistNativeHelper.TransitionSettings} 224 | */ 225 | TransitionSettings getTransitionSettings() { 226 | TransitionAlpha transitionAlpha = null; 227 | TransitionSliding transitionSliding = null; 228 | TransitionCrossfade transitionCrossfade = null; 229 | TransitionFadeBlack transitionFadeBlack = null; 230 | TransitionSettings transitionSetting = null; 231 | transitionSetting = new TransitionSettings(); 232 | transitionSetting.duration = (int)getDuration(); 233 | if (this instanceof TransitionAlpha) { 234 | transitionAlpha = (TransitionAlpha)this; 235 | transitionSetting.videoTransitionType = VideoTransition.ALPHA_MAGIC; 236 | transitionSetting.audioTransitionType = AudioTransition.CROSS_FADE; 237 | transitionSetting.transitionBehaviour = mNativeHelper 238 | .getVideoTransitionBehaviour(transitionAlpha.getBehavior()); 239 | transitionSetting.alphaSettings = new AlphaMagicSettings(); 240 | transitionSetting.slideSettings = null; 241 | transitionSetting.alphaSettings.file = transitionAlpha.getPNGMaskFilename(); 242 | transitionSetting.alphaSettings.blendingPercent = transitionAlpha.getBlendingPercent(); 243 | transitionSetting.alphaSettings.invertRotation = transitionAlpha.isInvert(); 244 | transitionSetting.alphaSettings.rgbWidth = transitionAlpha.getRGBFileWidth(); 245 | transitionSetting.alphaSettings.rgbHeight = transitionAlpha.getRGBFileHeight(); 246 | 247 | } else if (this instanceof TransitionSliding) { 248 | transitionSliding = (TransitionSliding)this; 249 | transitionSetting.videoTransitionType = VideoTransition.SLIDE_TRANSITION; 250 | transitionSetting.audioTransitionType = AudioTransition.CROSS_FADE; 251 | transitionSetting.transitionBehaviour = mNativeHelper 252 | .getVideoTransitionBehaviour(transitionSliding.getBehavior()); 253 | transitionSetting.alphaSettings = null; 254 | transitionSetting.slideSettings = new SlideTransitionSettings(); 255 | transitionSetting.slideSettings.direction = mNativeHelper 256 | .getSlideSettingsDirection(transitionSliding.getDirection()); 257 | } else if (this instanceof TransitionCrossfade) { 258 | transitionCrossfade = (TransitionCrossfade)this; 259 | transitionSetting.videoTransitionType = VideoTransition.CROSS_FADE; 260 | transitionSetting.audioTransitionType = AudioTransition.CROSS_FADE; 261 | transitionSetting.transitionBehaviour = mNativeHelper 262 | .getVideoTransitionBehaviour(transitionCrossfade.getBehavior()); 263 | transitionSetting.alphaSettings = null; 264 | transitionSetting.slideSettings = null; 265 | } else if (this instanceof TransitionFadeBlack) { 266 | transitionFadeBlack = (TransitionFadeBlack)this; 267 | transitionSetting.videoTransitionType = VideoTransition.FADE_BLACK; 268 | transitionSetting.audioTransitionType = AudioTransition.CROSS_FADE; 269 | transitionSetting.transitionBehaviour = mNativeHelper 270 | .getVideoTransitionBehaviour(transitionFadeBlack.getBehavior()); 271 | transitionSetting.alphaSettings = null; 272 | transitionSetting.slideSettings = null; 273 | } 274 | 275 | return transitionSetting; 276 | } 277 | 278 | /** 279 | * Checks if the effect and overlay applied on a media item 280 | * overlaps with the transition on media item. 281 | * 282 | * @param m The media item 283 | * @param clipSettings The ClipSettings object 284 | * @param clipNo The clip no.(out of the two media items 285 | * associated with current transition)for which the effect 286 | * clip should be generated 287 | * @return List of effects that overlap with the transition 288 | */ 289 | 290 | List isEffectandOverlayOverlapping(MediaItem m, ClipSettings clipSettings, 291 | int clipNo) { 292 | List effects; 293 | List overlays; 294 | List effectSettings = new ArrayList(); 295 | EffectSettings tmpEffectSettings; 296 | 297 | overlays = m.getAllOverlays(); 298 | for (Overlay overlay : overlays) { 299 | tmpEffectSettings = mNativeHelper.getOverlaySettings((OverlayFrame)overlay); 300 | mNativeHelper.adjustEffectsStartTimeAndDuration(tmpEffectSettings, 301 | clipSettings.beginCutTime, clipSettings.endCutTime); 302 | if (tmpEffectSettings.duration != 0) { 303 | effectSettings.add(tmpEffectSettings); 304 | } 305 | } 306 | 307 | effects = m.getAllEffects(); 308 | for (Effect effect : effects) { 309 | if (effect instanceof EffectColor) { 310 | tmpEffectSettings = mNativeHelper.getEffectSettings((EffectColor)effect); 311 | mNativeHelper.adjustEffectsStartTimeAndDuration(tmpEffectSettings, 312 | clipSettings.beginCutTime, clipSettings.endCutTime); 313 | if (tmpEffectSettings.duration != 0) { 314 | if (m instanceof MediaVideoItem) { 315 | tmpEffectSettings.fiftiesFrameRate = mNativeHelper 316 | .GetClosestVideoFrameRate(((MediaVideoItem)m).getFps()); 317 | } 318 | effectSettings.add(tmpEffectSettings); 319 | } 320 | } 321 | } 322 | 323 | return effectSettings; 324 | } 325 | 326 | /** 327 | * Generate the video clip for the specified transition. This method may 328 | * block for a significant amount of time. Before the method completes 329 | * execution it sets the mFilename to the name of the newly generated 330 | * transition video clip file. 331 | */ 332 | void generate() { 333 | MediaItem m1 = this.getAfterMediaItem(); 334 | MediaItem m2 = this.getBeforeMediaItem(); 335 | ClipSettings clipSettings1 = new ClipSettings(); 336 | ClipSettings clipSettings2 = new ClipSettings(); 337 | TransitionSettings transitionSetting = null; 338 | EditSettings editSettings = new EditSettings(); 339 | List effectSettings_clip1; 340 | List effectSettings_clip2; 341 | 342 | String output = null; 343 | 344 | if (mNativeHelper == null) { 345 | if (m1 != null) 346 | mNativeHelper = m1.getNativeContext(); 347 | else if (m2 != null) 348 | mNativeHelper = m2.getNativeContext(); 349 | } 350 | transitionSetting = getTransitionSettings(); 351 | if (m1 != null && m2 != null) { 352 | /* transition between media items */ 353 | clipSettings1 = m1.getClipSettings(); 354 | clipSettings2 = m2.getClipSettings(); 355 | clipSettings1.beginCutTime = (int)(clipSettings1.endCutTime - 356 | this.mDurationMs); 357 | clipSettings2.endCutTime = (int)(clipSettings2.beginCutTime + 358 | this.mDurationMs); 359 | /* 360 | * Check how many effects and overlays overlap with transition and 361 | * generate effect clip first if there is any overlap 362 | */ 363 | effectSettings_clip1 = isEffectandOverlayOverlapping(m1, clipSettings1,1); 364 | effectSettings_clip2 = isEffectandOverlayOverlapping(m2, clipSettings2,2); 365 | for (int index = 0; index < effectSettings_clip2.size(); index++ ) { 366 | effectSettings_clip2.get(index).startTime += this.mDurationMs; 367 | } 368 | editSettings.effectSettingsArray = 369 | new EffectSettings[effectSettings_clip1.size() 370 | + effectSettings_clip2.size()]; 371 | int i=0,j=0; 372 | while (i < effectSettings_clip1.size()) { 373 | editSettings.effectSettingsArray[j] = effectSettings_clip1.get(i); 374 | i++; 375 | j++; 376 | } 377 | i=0; 378 | while (i < effectSettings_clip2.size()) { 379 | editSettings.effectSettingsArray[j] = effectSettings_clip2.get(i); 380 | i++; 381 | j++; 382 | } 383 | } else if (m1 == null && m2 != null) { 384 | /* begin transition at first media item */ 385 | m2.generateBlankFrame(clipSettings1); 386 | clipSettings2 = m2.getClipSettings(); 387 | clipSettings1.endCutTime = (int)(this.mDurationMs + 50); 388 | clipSettings2.endCutTime = (int)(clipSettings2.beginCutTime + 389 | this.mDurationMs); 390 | /* 391 | * Check how many effects and overlays overlap with transition and 392 | * generate effect clip first if there is any overlap 393 | */ 394 | effectSettings_clip2 = isEffectandOverlayOverlapping(m2, clipSettings2,2); 395 | for (int index = 0; index < effectSettings_clip2.size(); index++ ) { 396 | effectSettings_clip2.get(index).startTime += this.mDurationMs; 397 | } 398 | editSettings.effectSettingsArray = new EffectSettings[effectSettings_clip2.size()]; 399 | int i=0, j=0; 400 | while (i < effectSettings_clip2.size()) { 401 | editSettings.effectSettingsArray[j] = effectSettings_clip2.get(i); 402 | i++; 403 | j++; 404 | } 405 | } else if (m1 != null && m2 == null) { 406 | /* end transition at last media item */ 407 | clipSettings1 = m1.getClipSettings(); 408 | m1.generateBlankFrame(clipSettings2); 409 | clipSettings1.beginCutTime = (int)(clipSettings1.endCutTime - 410 | this.mDurationMs); 411 | clipSettings2.endCutTime = (int)(this.mDurationMs + 50); 412 | /* 413 | * Check how many effects and overlays overlap with transition and 414 | * generate effect clip first if there is any overlap 415 | */ 416 | effectSettings_clip1 = isEffectandOverlayOverlapping(m1, clipSettings1,1); 417 | editSettings.effectSettingsArray = new EffectSettings[effectSettings_clip1.size()]; 418 | int i=0,j=0; 419 | while (i < effectSettings_clip1.size()) { 420 | editSettings.effectSettingsArray[j] = effectSettings_clip1.get(i); 421 | i++; 422 | j++; 423 | } 424 | } 425 | 426 | editSettings.clipSettingsArray = new ClipSettings[2]; 427 | editSettings.clipSettingsArray[0] = clipSettings1; 428 | editSettings.clipSettingsArray[1] = clipSettings2; 429 | editSettings.backgroundMusicSettings = null; 430 | editSettings.transitionSettingsArray = new TransitionSettings[1]; 431 | editSettings.transitionSettingsArray[0] = transitionSetting; 432 | output = mNativeHelper.generateTransitionClip(editSettings, mUniqueId, 433 | m1, m2,this); 434 | setFilename(output); 435 | } 436 | 437 | 438 | /** 439 | * Set the transition filename. 440 | */ 441 | void setFilename(String filename) { 442 | mFilename = filename; 443 | } 444 | 445 | /** 446 | * Get the transition filename. 447 | */ 448 | String getFilename() { 449 | return mFilename; 450 | } 451 | 452 | /** 453 | * Remove any resources associated with this transition 454 | */ 455 | void invalidate() { 456 | if (mFilename != null) { 457 | new File(mFilename).delete(); 458 | mFilename = null; 459 | } 460 | } 461 | 462 | /** 463 | * Check if the transition is generated. 464 | * 465 | * @return true if the transition is generated 466 | */ 467 | boolean isGenerated() { 468 | return (mFilename != null); 469 | } 470 | 471 | /* 472 | * {@inheritDoc} 473 | */ 474 | @Override 475 | public boolean equals(Object object) { 476 | if (!(object instanceof Transition)) { 477 | return false; 478 | } 479 | return mUniqueId.equals(((Transition)object).mUniqueId); 480 | } 481 | 482 | /* 483 | * {@inheritDoc} 484 | */ 485 | @Override 486 | public int hashCode() { 487 | return mUniqueId.hashCode(); 488 | } 489 | } 490 | -------------------------------------------------------------------------------- /src/android/media/videoeditor/TransitionAlpha.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 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 | 17 | package android.media.videoeditor; 18 | 19 | import android.graphics.Bitmap; 20 | import android.graphics.BitmapFactory; 21 | import java.io.DataOutputStream; 22 | import java.io.File; 23 | import java.io.FileOutputStream; 24 | import java.io.IOException; 25 | import java.nio.ByteBuffer; 26 | import java.nio.IntBuffer; 27 | 28 | /** 29 | * This class allows to render an "alpha blending" transition according to a 30 | * bitmap mask. The mask shows the shape of the transition all along the 31 | * duration of the transition: just before the transition, video 1 is fully 32 | * displayed. When the transition starts, as the time goes on, pixels of video 2 33 | * replace pixels of video 1 according to the gray scale pixel value of the 34 | * mask. 35 | * {@hide} 36 | */ 37 | public class TransitionAlpha extends Transition { 38 | /** This is the input JPEG file for the mask */ 39 | private final String mMaskFilename; 40 | 41 | /** 42 | * This is percentage (between 0 and 100) of blending between video 1 and 43 | * video 2 if this value equals 0, then the mask is strictly applied if this 44 | * value equals 100, then the mask is not at all applied (no transition 45 | * effect) 46 | */ 47 | private final int mBlendingPercent; 48 | 49 | /** 50 | * If true, this value inverts the direction of the mask: white pixels of 51 | * the mask show video 2 pixels first black pixels of the mask show video 2 52 | * pixels last. 53 | */ 54 | private final boolean mIsInvert; 55 | 56 | 57 | private int mWidth; 58 | private int mHeight; 59 | private String mRGBMaskFile; 60 | 61 | /** 62 | * An object of this type cannot be instantiated by using the default 63 | * constructor 64 | */ 65 | @SuppressWarnings("unused") 66 | private TransitionAlpha() { 67 | this(null, null, null, 0, 0, null, 0, false); 68 | } 69 | 70 | /** 71 | * Constructor 72 | * 73 | * @param transitionId The transition id 74 | * @param afterMediaItem The transition is applied to the end of this media 75 | * item 76 | * @param beforeMediaItem The transition is applied to the beginning of this 77 | * media item 78 | * @param durationMs duration of the transition in milliseconds 79 | * @param behavior behavior is one of the behavior defined in Transition 80 | * class 81 | * @param maskFilename JPEG file name. The dimension of the image 82 | * corresponds to 720p (16:9 aspect ratio). Mask files are 83 | * shared between video editors and can be created in the 84 | * projects folder (the parent folder for all projects). 85 | * @param blendingPercent The blending percent applied 86 | * @param invert true to invert the direction of the alpha blending 87 | * @throws IllegalArgumentException if behavior is not supported, or if 88 | * direction are not supported. 89 | */ 90 | public TransitionAlpha(String transitionId, MediaItem afterMediaItem, 91 | MediaItem beforeMediaItem, long durationMs, int behavior, 92 | String maskFilename, int blendingPercent, boolean invert) { 93 | super(transitionId, afterMediaItem, beforeMediaItem, durationMs, behavior); 94 | 95 | /** 96 | * Generate a RGB file for the supplied mask file 97 | */ 98 | final BitmapFactory.Options dbo = new BitmapFactory.Options(); 99 | dbo.inJustDecodeBounds = true; 100 | if (!new File(maskFilename).exists()) 101 | throw new IllegalArgumentException("File not Found " + maskFilename); 102 | BitmapFactory.decodeFile(maskFilename, dbo); 103 | 104 | mWidth = dbo.outWidth; 105 | mHeight = dbo.outHeight; 106 | 107 | mRGBMaskFile = String.format(mNativeHelper.getProjectPath() + 108 | "/" + "mask" + transitionId+ ".rgb"); 109 | 110 | 111 | FileOutputStream fl = null; 112 | 113 | try{ 114 | fl = new FileOutputStream(mRGBMaskFile); 115 | } catch (IOException e) { 116 | /* catch IO exception */ 117 | } 118 | final DataOutputStream dos = new DataOutputStream(fl); 119 | 120 | if (fl != null) { 121 | /** 122 | * Write to rgb file 123 | */ 124 | Bitmap imageBitmap = BitmapFactory.decodeFile(maskFilename); 125 | final int [] framingBuffer = new int[mWidth]; 126 | ByteBuffer byteBuffer = ByteBuffer.allocate(framingBuffer.length * 4); 127 | IntBuffer intBuffer; 128 | 129 | byte[] array = byteBuffer.array(); 130 | int tmp = 0; 131 | while (tmp < mHeight) { 132 | imageBitmap.getPixels(framingBuffer, 0, mWidth, 0, tmp,mWidth, 1); 133 | intBuffer = byteBuffer.asIntBuffer(); 134 | intBuffer.put(framingBuffer,0,mWidth); 135 | try { 136 | dos.write(array); 137 | } catch (IOException e) { 138 | /* catch file write error */ 139 | } 140 | tmp += 1; 141 | } 142 | 143 | imageBitmap.recycle(); 144 | try{ 145 | fl.close(); 146 | }catch (IOException e) { 147 | /* file close error */ 148 | } 149 | } 150 | 151 | /** 152 | * Capture the details 153 | */ 154 | mMaskFilename = maskFilename; 155 | mBlendingPercent = blendingPercent; 156 | mIsInvert = invert; 157 | } 158 | 159 | public int getRGBFileWidth() { 160 | return mWidth; 161 | } 162 | 163 | public int getRGBFileHeight() { 164 | return mHeight; 165 | } 166 | 167 | public String getPNGMaskFilename() { 168 | return mRGBMaskFile; 169 | } 170 | 171 | /** 172 | * Get the blending percentage 173 | * 174 | * @return The blending percentage 175 | */ 176 | public int getBlendingPercent() { 177 | return mBlendingPercent; 178 | } 179 | 180 | /** 181 | * Get the filename of the mask. 182 | * 183 | * @return The mask filename 184 | */ 185 | public String getMaskFilename() { 186 | return mMaskFilename; 187 | } 188 | 189 | /** 190 | * Check if the alpha blending direction is inverted. 191 | * 192 | * @return true if the direction of the alpha blending is inverted 193 | */ 194 | public boolean isInvert() { 195 | return mIsInvert; 196 | } 197 | 198 | /* 199 | * {@inheritDoc} 200 | */ 201 | @Override 202 | public void generate() { 203 | super.generate(); 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/android/media/videoeditor/TransitionCrossfade.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 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 | 17 | 18 | package android.media.videoeditor; 19 | 20 | 21 | /** 22 | * This class allows to render a crossfade (dissolve) effect transition between 23 | * two videos 24 | * {@hide} 25 | */ 26 | public class TransitionCrossfade extends Transition { 27 | /** 28 | * An object of this type cannot be instantiated by using the default 29 | * constructor 30 | */ 31 | @SuppressWarnings("unused") 32 | private TransitionCrossfade() { 33 | this(null, null, null, 0, 0); 34 | } 35 | 36 | /** 37 | * Constructor 38 | * 39 | * @param transitionId The transition id 40 | * @param afterMediaItem The transition is applied to the end of this 41 | * media item 42 | * @param beforeMediaItem The transition is applied to the beginning of 43 | * this media item 44 | * @param durationMs duration of the transition in milliseconds 45 | * @param behavior behavior is one of the behavior defined in Transition 46 | * class 47 | * 48 | * @throws IllegalArgumentException if behavior is not supported. 49 | */ 50 | public TransitionCrossfade(String transitionId, MediaItem afterMediaItem, 51 | MediaItem beforeMediaItem, long durationMs, int behavior) { 52 | super(transitionId, afterMediaItem, beforeMediaItem, durationMs, behavior); 53 | } 54 | 55 | /* 56 | * {@inheritDoc} 57 | */ 58 | @Override 59 | void generate() { 60 | super.generate(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/android/media/videoeditor/TransitionFadeBlack.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 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 | 17 | 18 | package android.media.videoeditor; 19 | 20 | 21 | /** 22 | * This class is used to render a fade to black and fade from black transition 23 | * between two media items. 24 | * {@hide} 25 | */ 26 | public class TransitionFadeBlack extends Transition { 27 | /** 28 | * An object of this type cannot be instantiated by using the default 29 | * constructor 30 | */ 31 | @SuppressWarnings("unused") 32 | private TransitionFadeBlack() { 33 | this(null, null, null, 0, 0); 34 | } 35 | 36 | /** 37 | * Constructor 38 | * 39 | * @param transitionId The transition id 40 | * @param afterMediaItem The transition is applied to the end of this 41 | * media item 42 | * @param beforeMediaItem The transition is applied to the beginning of 43 | * this media item 44 | * @param durationMs duration of the transition 45 | * @param behavior behavior is one of the behavior defined in Transition 46 | * class 47 | * 48 | * @throws IllegalArgumentException if behavior is not supported. 49 | */ 50 | public TransitionFadeBlack(String transitionId, MediaItem afterMediaItem, 51 | MediaItem beforeMediaItem, long durationMs, int behavior) { 52 | super(transitionId, afterMediaItem, beforeMediaItem, durationMs, behavior); 53 | } 54 | 55 | /* 56 | * {@inheritDoc} 57 | */ 58 | @Override 59 | void generate() { 60 | super.generate(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/android/media/videoeditor/TransitionSliding.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 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 | 17 | package android.media.videoeditor; 18 | 19 | /** 20 | * This class allows to create sliding transitions 21 | * {@hide} 22 | */ 23 | public class TransitionSliding extends Transition { 24 | 25 | /** Video 1 is pushed to the right while video 2 is coming from left */ 26 | public final static int DIRECTION_RIGHT_OUT_LEFT_IN = 0; 27 | /** Video 1 is pushed to the left while video 2 is coming from right */ 28 | public static final int DIRECTION_LEFT_OUT_RIGHT_IN = 1; 29 | /** Video 1 is pushed to the top while video 2 is coming from bottom */ 30 | public static final int DIRECTION_TOP_OUT_BOTTOM_IN = 2; 31 | /** Video 1 is pushed to the bottom while video 2 is coming from top */ 32 | public static final int DIRECTION_BOTTOM_OUT_TOP_IN = 3; 33 | 34 | // The sliding transitions 35 | private final int mSlidingDirection; 36 | 37 | /** 38 | * An object of this type cannot be instantiated by using the default 39 | * constructor 40 | */ 41 | @SuppressWarnings("unused") 42 | private TransitionSliding() { 43 | this(null, null, null, 0, 0, 0); 44 | } 45 | 46 | /** 47 | * Constructor 48 | * 49 | * @param transitionId The transition id 50 | * @param afterMediaItem The transition is applied to the end of this 51 | * media item 52 | * @param beforeMediaItem The transition is applied to the beginning of 53 | * this media item 54 | * @param durationMs duration of the transition in milliseconds 55 | * @param behavior behavior is one of the behavior defined in Transition 56 | * class 57 | * @param direction direction shall be one of the supported directions like 58 | * RIGHT_OUT_LEFT_IN 59 | * 60 | * @throws IllegalArgumentException if behavior is not supported. 61 | */ 62 | public TransitionSliding(String transitionId, MediaItem afterMediaItem, 63 | MediaItem beforeMediaItem, long durationMs, int behavior, 64 | int direction) { 65 | super(transitionId, afterMediaItem, beforeMediaItem, durationMs, behavior); 66 | switch (direction) { 67 | case DIRECTION_RIGHT_OUT_LEFT_IN: 68 | case DIRECTION_LEFT_OUT_RIGHT_IN: 69 | case DIRECTION_TOP_OUT_BOTTOM_IN: 70 | case DIRECTION_BOTTOM_OUT_TOP_IN: 71 | break; 72 | 73 | default: 74 | throw new IllegalArgumentException("Invalid direction"); 75 | } 76 | mSlidingDirection = direction; 77 | } 78 | 79 | /** 80 | * Get the sliding direction. 81 | * 82 | * @return The sliding direction 83 | */ 84 | public int getDirection() { 85 | return mSlidingDirection; 86 | } 87 | 88 | /* 89 | * {@inheritDoc} 90 | */ 91 | @Override 92 | void generate() { 93 | super.generate(); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/android/media/videoeditor/VideoEditorFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 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 | 17 | 18 | package android.media.videoeditor; 19 | 20 | import java.io.File; 21 | import java.io.FileNotFoundException; 22 | import java.io.IOException; 23 | 24 | import android.media.videoeditor.VideoEditor.MediaProcessingProgressListener; 25 | 26 | /** 27 | * The VideoEditorFactory class must be used to instantiate VideoEditor objects 28 | * by creating a new project {@link #create(String)} or by loading an 29 | * existing project {@link #load(String)}. 30 | * {@hide} 31 | */ 32 | public class VideoEditorFactory { 33 | /** 34 | * This is the factory method for creating a new VideoEditor instance. 35 | * 36 | * @param projectPath The path where all VideoEditor internal 37 | * files are stored. When a project is deleted the application is 38 | * responsible for deleting the path and its contents. 39 | * 40 | * @return The VideoEditor instance 41 | * 42 | * @throws IOException if path does not exist or if the path can 43 | * not be accessed in read/write mode 44 | */ 45 | public static VideoEditor create(String projectPath) throws IOException { 46 | /* 47 | * If the project path does not exist create it 48 | */ 49 | final File dir = new File(projectPath); 50 | if (!dir.exists()) { 51 | if (!dir.mkdirs()) { 52 | throw new FileNotFoundException("Cannot create project path: " 53 | + projectPath); 54 | } else { 55 | /* 56 | * Create the file which hides the media files 57 | * from the media scanner 58 | */ 59 | if (!new File(dir, ".nomedia").createNewFile()) { 60 | throw new FileNotFoundException("Cannot create file .nomedia"); 61 | } 62 | } 63 | } 64 | 65 | return new VideoEditorImpl(projectPath); 66 | } 67 | 68 | /** 69 | * This is the factory method for instantiating a VideoEditor from the 70 | * internal state previously saved with the 71 | * {@link VideoEditor#save(String)} method. 72 | * 73 | * @param projectPath The path where all VideoEditor internal files 74 | * are stored. When a project is deleted the application is 75 | * responsible for deleting the path and its contents. 76 | * @param generatePreview if set to true the 77 | * {@link MediaEditor#generatePreview(MediaProcessingProgressListener 78 | * listener)} 79 | * will be called internally to generate any needed transitions. 80 | * 81 | * @return The VideoEditor instance 82 | * 83 | * @throws IOException if path does not exist or if the path can 84 | * not be accessed in read/write mode or if one of the resource 85 | * media files cannot be retrieved 86 | */ 87 | public static VideoEditor load(String projectPath, boolean generatePreview) 88 | throws IOException { 89 | final VideoEditor videoEditor = new VideoEditorImpl(projectPath); 90 | if (generatePreview) { 91 | videoEditor.generatePreview(null); 92 | } 93 | return videoEditor; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/android/media/videoeditor/VideoEditorProfile.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 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 | package android.media.videoeditor; 17 | 18 | /** 19 | * The VideoEditorProfile class is used to retrieve the 20 | * predefined videoeditor profile settings for videoeditor applications. 21 | * These settings are read-only. 22 | * 23 | *

The videoeditor profile specifies the following set of parameters: 24 | *

    25 | *
  • max input video frame width 26 | *
  • max input video frame height 27 | *
  • max output video frame width 28 | *
  • max output video frame height 29 | *
30 | * {@hide} 31 | */ 32 | public class VideoEditorProfile 33 | { 34 | static { 35 | System.loadLibrary("media_jni"); 36 | native_init(); 37 | } 38 | /** 39 | * The max input video frame width 40 | */ 41 | public int maxInputVideoFrameWidth; 42 | 43 | /** 44 | * The max input video frame height 45 | */ 46 | public int maxInputVideoFrameHeight; 47 | 48 | /** 49 | * The max ouput video frame width 50 | */ 51 | public int maxOutputVideoFrameWidth; 52 | 53 | /** 54 | * The max ouput video frame height 55 | */ 56 | public int maxOutputVideoFrameHeight; 57 | 58 | /** 59 | * Returns the videoeditor profile 60 | */ 61 | public static VideoEditorProfile get() { 62 | return native_get_videoeditor_profile(); 63 | } 64 | 65 | /** 66 | * Returns the supported profile by given video codec 67 | */ 68 | public static int getExportProfile(int vidCodec) { 69 | int profile = -1; 70 | 71 | switch (vidCodec) { 72 | case MediaProperties.VCODEC_H263: 73 | case MediaProperties.VCODEC_H264: 74 | case MediaProperties.VCODEC_MPEG4: 75 | profile = native_get_videoeditor_export_profile(vidCodec); 76 | break; 77 | default : 78 | throw new IllegalArgumentException("Unsupported video codec" + vidCodec); 79 | } 80 | 81 | return profile; 82 | } 83 | 84 | /** 85 | * Returns the supported level by given video codec 86 | */ 87 | public static int getExportLevel(int vidCodec) { 88 | int level = -1; 89 | 90 | switch (vidCodec) { 91 | case MediaProperties.VCODEC_H263: 92 | case MediaProperties.VCODEC_H264: 93 | case MediaProperties.VCODEC_MPEG4: 94 | level = native_get_videoeditor_export_level(vidCodec); 95 | break; 96 | default : 97 | throw new IllegalArgumentException("Unsupported video codec" + vidCodec); 98 | } 99 | 100 | return level; 101 | } 102 | 103 | // Private constructor called by JNI 104 | private VideoEditorProfile(int inputWidth, 105 | int inputHeight, 106 | int outputWidth, 107 | int outputHeight) { 108 | 109 | this.maxInputVideoFrameWidth = inputWidth; 110 | this.maxInputVideoFrameHeight = inputHeight; 111 | this.maxOutputVideoFrameWidth = outputWidth; 112 | this.maxOutputVideoFrameHeight = outputHeight; 113 | } 114 | 115 | // Methods implemented by JNI 116 | private static native final void native_init(); 117 | private static native final VideoEditorProfile 118 | native_get_videoeditor_profile(); 119 | private static native final int native_get_videoeditor_export_profile(int codec); 120 | private static native final int native_get_videoeditor_export_level(int level); 121 | 122 | } 123 | -------------------------------------------------------------------------------- /src/android/media/videoeditor/WaveformData.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 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 | 17 | 18 | package android.media.videoeditor; 19 | 20 | import java.io.File; 21 | import java.io.FileInputStream; 22 | import java.io.IOException; 23 | 24 | /** 25 | * Class which describes the waveform data of an audio track. The gain values 26 | * represent the average gain for an audio frame. For audio codecs which do not 27 | * operate on a per frame bases (eg. ALAW, ULAW) a reasonable audio frame 28 | * duration will be assumed (eg. 50ms). 29 | * {@hide} 30 | */ 31 | public class WaveformData { 32 | /* 33 | * Instance variables 34 | */ 35 | private final int mFrameDurationMs; 36 | private final int mFramesCount; 37 | private final short[] mGains; 38 | 39 | /* 40 | * This constructor shall not be used 41 | */ 42 | @SuppressWarnings("unused") 43 | private WaveformData() throws IOException { 44 | mFrameDurationMs = 0; 45 | mFramesCount = 0; 46 | mGains = null; 47 | } 48 | 49 | /* 50 | * Constructor 51 | * 52 | * @param audioWaveformFilename The name of the audio waveform file 53 | * 54 | * The file format is as following: 55 | *
    56 | *
  • first 4 bytes provide the number of samples for each value, as 57 | * big-endian signed
  • 58 | *
  • 4 following bytes is the total number of values in the file, as 59 | * big-endian signed
  • 60 | *
  • then, all values follow as bytes
  • 61 | *
62 | * 63 | * @throws IOException on failure of file input stream operations 64 | * @throws IllegalArgumentException if audioWaveformFilename is null 65 | */ 66 | WaveformData(String audioWaveformFilename) throws IOException { 67 | 68 | if (audioWaveformFilename == null) { 69 | throw new IllegalArgumentException("WaveformData : filename is null"); 70 | } 71 | 72 | FileInputStream audioGraphFileReadHandle = null; 73 | 74 | try { 75 | final File audioGraphFileContext = new File(audioWaveformFilename); 76 | 77 | audioGraphFileReadHandle = new FileInputStream(audioGraphFileContext); 78 | /* 79 | * Read frame duration 80 | */ 81 | final byte tempFrameDuration[] = new byte[4]; 82 | 83 | audioGraphFileReadHandle.read(tempFrameDuration, 0, 4); 84 | 85 | int tempFrameDurationMs = 0; 86 | int tempFramesCounter = 0; 87 | for (int i = 0; i < 4; i++) { 88 | tempFrameDurationMs = (tempFrameDurationMs << 8); 89 | tempFrameDurationMs = (tempFrameDurationMs | (tempFrameDuration[i] & 0xff)); 90 | } 91 | mFrameDurationMs = tempFrameDurationMs; 92 | 93 | /* 94 | * Read count 95 | */ 96 | final byte tempFramesCount[] = new byte[4]; 97 | 98 | audioGraphFileReadHandle.read(tempFramesCount, 0, 4); 99 | for (int i = 0; i < 4; i++) { 100 | tempFramesCounter = (tempFramesCounter << 8); 101 | tempFramesCounter = (tempFramesCounter | (tempFramesCount[i] & 0xff)); 102 | } 103 | mFramesCount = tempFramesCounter; 104 | 105 | /* 106 | * Capture the graph values 107 | */ 108 | mGains = new short[mFramesCount]; 109 | 110 | for (int i = 0; i < mFramesCount; i++) { 111 | mGains[i] = (short)audioGraphFileReadHandle.read(); 112 | } 113 | } finally { 114 | if (audioGraphFileReadHandle != null) { 115 | audioGraphFileReadHandle.close(); 116 | } 117 | } 118 | } 119 | 120 | /** 121 | * @return The duration of a frame in milliseconds 122 | */ 123 | public int getFrameDuration() { 124 | return mFrameDurationMs; 125 | } 126 | 127 | /** 128 | * @return The number of frames within the waveform data 129 | */ 130 | public int getFramesCount() { 131 | return mFramesCount; 132 | } 133 | 134 | /** 135 | * @return The array of frame gains. The size of the array is the frames 136 | * count. The values of the frame gains range from 0 to 255. 137 | */ 138 | public short[] getFrameGains() { 139 | return mGains; 140 | } 141 | } 142 | --------------------------------------------------------------------------------