diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..a79483bd
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,17 @@
+## 0.3.0
+- Fix cancel() sometimes not working. (Thanks @strayerM and @PinkFloyded)
+- Geolocation support on API>=19. (Thanks @hkurokawa)
+
+## 0.2.0
+- Experimental audio transcoding support. (Thanks @aaron112)
+- Fix transcode does not run on Huawei Ascend P7. (Thanks @spiritedRunning)
+- Fix race condition caused by not closing output before callback. (Thanks @ryanwilliams83)
+
+## 0.1.10
+- `Future` support. (Thanks @MaiKambayashi)
+
+## 0.1.X
+- Stability updates. (Thanks @ozyozyo)
+
+## 0.1.0
+- First release.
diff --git a/README.md b/README.md
index df1bb947..2d72b83d 100644
--- a/README.md
+++ b/README.md
@@ -67,7 +67,7 @@ repositories {
```
```groovy
-compile 'net.ypresto.androidtranscoder:android-transcoder:0.1.10'
+compile 'net.ypresto.androidtranscoder:android-transcoder:0.2.0'
```
## Note (PLEASE READ FIRST)
@@ -78,6 +78,20 @@ Use [qtfaststart-java](https://github.com/ypresto/qtfaststart-java) to place moo
- Android does not gurantees that all devices have bug-free codecs/accelerators for your codec parameters (especially, resolution). Refer [supported media formats](http://developer.android.com/guide/appendix/media-formats.html) for parameters guaranteed by [CTS](https://source.android.com/compatibility/cts-intro.html).
- This library does not support video files recorded by other device like digital cameras, iOS (mov files, including non-baseline profile h.264), etc.
+
+## More information about internals
+
+There is a blog post about this library written in Japanese.
+http://qiita.com/yuya_presto/items/d48e29c89109b746d000
+
+While it is Japanese, diagrams would be useful for understanding internals of this library.
+
+## References for Android Low-Level Media APIs
+
+- http://bigflake.com/mediacodec/
+- https://github.com/google/grafika
+- https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright
+
## License
```
@@ -95,9 +109,3 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```
-
-## References for Android Low-Level Media APIs
-
-- http://bigflake.com/mediacodec/
-- https://github.com/google/grafika
-- https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright
diff --git a/build.gradle b/build.gradle
index e220f0b8..13089606 100644
--- a/build.gradle
+++ b/build.gradle
@@ -3,9 +3,13 @@
buildscript {
repositories {
jcenter()
+ maven {
+ url 'https://maven.google.com/'
+ name 'Google'
+ }
}
dependencies {
- classpath 'com.android.tools.build:gradle:2.1.2'
+ classpath 'com.android.tools.build:gradle:2.3.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
@@ -15,5 +19,9 @@ buildscript {
allprojects {
repositories {
jcenter()
+ maven {
+ url 'https://maven.google.com/'
+ name 'Google'
+ }
}
}
diff --git a/example/build.gradle b/example/build.gradle
index e0179e52..8560676b 100644
--- a/example/build.gradle
+++ b/example/build.gradle
@@ -2,7 +2,7 @@ apply plugin: 'com.android.application'
android {
compileSdkVersion 24
- buildToolsVersion "24.0.1"
+ buildToolsVersion '25.0.0'
defaultConfig {
applicationId "net.ypresto.androidtranscoder.example"
@@ -21,4 +21,5 @@ android {
dependencies {
compile project(':lib')
+ compile 'com.android.support:support-core-utils:24.2.0'
}
diff --git a/example/src/main/AndroidManifest.xml b/example/src/main/AndroidManifest.xml
index 19c33591..498d9288 100644
--- a/example/src/main/AndroidManifest.xml
+++ b/example/src/main/AndroidManifest.xml
@@ -1,21 +1,35 @@
+ package="net.ypresto.androidtranscoder.example">
+
+
+ android:theme="@style/AppTheme">
+ android:label="@string/app_name">
+
+
+
+
diff --git a/example/src/main/java/net/ypresto/androidtranscoder/example/TranscoderActivity.java b/example/src/main/java/net/ypresto/androidtranscoder/example/TranscoderActivity.java
index f67f1543..7e08566b 100644
--- a/example/src/main/java/net/ypresto/androidtranscoder/example/TranscoderActivity.java
+++ b/example/src/main/java/net/ypresto/androidtranscoder/example/TranscoderActivity.java
@@ -7,6 +7,7 @@
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
+import android.support.v4.content.FileProvider;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
@@ -26,6 +27,7 @@
public class TranscoderActivity extends Activity {
private static final String TAG = "TranscoderActivity";
+ private static final String FILE_PROVIDER_AUTHORITY = "net.ypresto.androidtranscoder.example.fileprovider";
private static final int REQUEST_CODE_PICK = 1;
private static final int PROGRESS_BAR_MAX = 1000;
private Future mFuture;
@@ -55,7 +57,10 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
final File file;
if (resultCode == RESULT_OK) {
try {
- file = File.createTempFile("transcode_test", ".mp4", getExternalFilesDir(null));
+ File outputDir = new File(getExternalFilesDir(null), "outputs");
+ //noinspection ResultOfMethodCallIgnored
+ outputDir.mkdir();
+ file = File.createTempFile("transcode_test", ".mp4", outputDir);
} catch (IOException e) {
Log.e(TAG, "Failed to create temporary file.", e);
Toast.makeText(this, "Failed to create temporary file.", Toast.LENGTH_LONG).show();
@@ -89,7 +94,10 @@ public void onTranscodeProgress(double progress) {
public void onTranscodeCompleted() {
Log.d(TAG, "transcoding took " + (SystemClock.uptimeMillis() - startTime) + "ms");
onTranscodeFinished(true, "transcoded file placed on " + file, parcelFileDescriptor);
- startActivity(new Intent(Intent.ACTION_VIEW).setDataAndType(Uri.fromFile(file), "video/mp4"));
+ Uri uri = FileProvider.getUriForFile(TranscoderActivity.this, FILE_PROVIDER_AUTHORITY, file);
+ startActivity(new Intent(Intent.ACTION_VIEW)
+ .setDataAndType(uri, "video/mp4")
+ .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION));
}
@Override
diff --git a/example/src/main/res/xml/file_paths.xml b/example/src/main/res/xml/file_paths.xml
new file mode 100644
index 00000000..5b254ecc
--- /dev/null
+++ b/example/src/main/res/xml/file_paths.xml
@@ -0,0 +1,6 @@
+
+
+
+
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index d14f516d..9555d195 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Mon Mar 09 14:43:12 JST 2015
+#Thu Jun 08 12:40:11 IST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
diff --git a/lib/build.gradle b/lib/build.gradle
index dfc02d9e..1ab6c7ba 100644
--- a/lib/build.gradle
+++ b/lib/build.gradle
@@ -12,7 +12,7 @@ apply plugin: 'bintray-release'
android {
compileSdkVersion 24
- buildToolsVersion "24.0.1"
+ buildToolsVersion '25.0.2'
defaultConfig {
minSdkVersion 18
@@ -32,7 +32,7 @@ android {
publish {
groupId = 'net.ypresto.androidtranscoder'
artifactId = 'android-transcoder'
- version = '0.1.10-SNAPSHOT'
+ version = '0.3.0'
licences = ['Apache-2.0']
website = 'https://github.com/ypresto/android-transcoder'
autoPublish = false
diff --git a/lib/src/androidTest/java/net/ypresto/androidtranscoder/utils/ISO6709LocationParserTest.java b/lib/src/androidTest/java/net/ypresto/androidtranscoder/utils/ISO6709LocationParserTest.java
new file mode 100644
index 00000000..a1a9caae
--- /dev/null
+++ b/lib/src/androidTest/java/net/ypresto/androidtranscoder/utils/ISO6709LocationParserTest.java
@@ -0,0 +1,35 @@
+package net.ypresto.androidtranscoder.utils;
+
+import junit.framework.TestCase;
+
+public class ISO6709LocationParserTest extends TestCase {
+ public void testParse() {
+ ISO6709LocationParser parser = new ISO6709LocationParser();
+ assertEquals(new float[]{35.658632f, 139.745411f}, parser.parse("+35.658632+139.745411/"));
+ assertEquals(new float[]{40.75f, -074.00f}, parser.parse("+40.75-074.00/"));
+ // with Altitude
+ assertEquals(new float[]{-90f, +0f}, parser.parse("-90+000+2800/"));
+ assertEquals(new float[]{27.5916f, 086.5640f}, parser.parse("+27.5916+086.5640+8850/"));
+ // ranged data
+ assertEquals(new float[]{35.331f, 134.224f}, parser.parse("+35.331+134.224/+35.336+134.228/"));
+ assertEquals(new float[]{35.331f, 134.224f}, parser.parse("+35.331+134.224/+35.336+134.228/+35.333+134.229/+35.333+134.227/"));
+ }
+
+ public void testParseFailure() {
+ ISO6709LocationParser parser = new ISO6709LocationParser();
+ assertNull(parser.parse(null));
+ assertNull(parser.parse(""));
+ assertNull(parser.parse("35 deg 65' 86.32\" N, 139 deg 74' 54.11\" E"));
+ assertNull(parser.parse("+35.658632"));
+ assertNull(parser.parse("+35.658632-"));
+ assertNull(parser.parse("40.75-074.00"));
+ assertNull(parser.parse("+40.75-074.00.00"));
+ }
+
+ private static void assertEquals(float[] expected, float[] actual) {
+ assertEquals(expected.length, actual.length);
+ for (int i = 0; i < expected.length; i++) {
+ assertTrue(Float.compare(expected[i], actual[i]) == 0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/src/main/java/net/ypresto/androidtranscoder/MediaTranscoder.java b/lib/src/main/java/net/ypresto/androidtranscoder/MediaTranscoder.java
index 3a758677..7ec32bc9 100644
--- a/lib/src/main/java/net/ypresto/androidtranscoder/MediaTranscoder.java
+++ b/lib/src/main/java/net/ypresto/androidtranscoder/MediaTranscoder.java
@@ -123,20 +123,20 @@ public void onTranscodeProgress(double progress) {
@Override
public void onTranscodeCompleted() {
- listener.onTranscodeCompleted();
closeStream();
+ listener.onTranscodeCompleted();
}
@Override
public void onTranscodeCanceled() {
- listener.onTranscodeCanceled();
closeStream();
+ listener.onTranscodeCanceled();
}
@Override
public void onTranscodeFailed(Exception exception) {
- listener.onTranscodeFailed(exception);
closeStream();
+ listener.onTranscodeFailed(exception);
}
private void closeStream() {
diff --git a/lib/src/main/java/net/ypresto/androidtranscoder/engine/AudioTrackTranscoder.java b/lib/src/main/java/net/ypresto/androidtranscoder/engine/AudioTrackTranscoder.java
index 47e0a61d..99034ef0 100644
--- a/lib/src/main/java/net/ypresto/androidtranscoder/engine/AudioTrackTranscoder.java
+++ b/lib/src/main/java/net/ypresto/androidtranscoder/engine/AudioTrackTranscoder.java
@@ -79,7 +79,7 @@ public void setup() {
@Override
public MediaFormat getDeterminedFormat() {
- return mInputFormat;
+ return mActualOutputFormat;
}
@Override
diff --git a/lib/src/main/java/net/ypresto/androidtranscoder/engine/MediaFormatValidator.java b/lib/src/main/java/net/ypresto/androidtranscoder/engine/MediaFormatValidator.java
index 67ea5ba8..6182ee60 100644
--- a/lib/src/main/java/net/ypresto/androidtranscoder/engine/MediaFormatValidator.java
+++ b/lib/src/main/java/net/ypresto/androidtranscoder/engine/MediaFormatValidator.java
@@ -18,14 +18,8 @@
import android.media.MediaFormat;
import net.ypresto.androidtranscoder.format.MediaFormatExtraConstants;
-import net.ypresto.androidtranscoder.utils.AvcCsdUtils;
-import net.ypresto.androidtranscoder.utils.AvcSpsUtils;
-
-import java.nio.ByteBuffer;
class MediaFormatValidator {
- // Refer: http://en.wikipedia.org/wiki/H.264/MPEG-4_AVC#Profiles
- private static final byte PROFILE_IDC_BASELINE = 66;
public static void validateVideoOutputFormat(MediaFormat format) {
String mime = format.getString(MediaFormat.KEY_MIME);
@@ -34,11 +28,6 @@ public static void validateVideoOutputFormat(MediaFormat format) {
if (!MediaFormatExtraConstants.MIMETYPE_VIDEO_AVC.equals(mime)) {
throw new InvalidOutputFormatException("Video codecs other than AVC is not supported, actual mime type: " + mime);
}
- ByteBuffer spsBuffer = AvcCsdUtils.getSpsBuffer(format);
- byte profileIdc = AvcSpsUtils.getProfileIdc(spsBuffer);
- if (profileIdc != PROFILE_IDC_BASELINE) {
- throw new InvalidOutputFormatException("Non-baseline AVC video profile is not supported by Android OS, actual profile_idc: " + profileIdc);
- }
}
public static void validateAudioOutputFormat(MediaFormat format) {
diff --git a/lib/src/main/java/net/ypresto/androidtranscoder/engine/MediaTranscoderEngine.java b/lib/src/main/java/net/ypresto/androidtranscoder/engine/MediaTranscoderEngine.java
index c883979c..812c31d4 100644
--- a/lib/src/main/java/net/ypresto/androidtranscoder/engine/MediaTranscoderEngine.java
+++ b/lib/src/main/java/net/ypresto/androidtranscoder/engine/MediaTranscoderEngine.java
@@ -19,9 +19,12 @@
import android.media.MediaFormat;
import android.media.MediaMetadataRetriever;
import android.media.MediaMuxer;
+import android.os.Build;
import android.util.Log;
+import net.ypresto.androidtranscoder.BuildConfig;
import net.ypresto.androidtranscoder.format.MediaFormatStrategy;
+import net.ypresto.androidtranscoder.utils.ISO6709LocationParser;
import net.ypresto.androidtranscoder.utils.MediaExtractorUtils;
import java.io.FileDescriptor;
@@ -137,9 +140,17 @@ private void setupMetadata() throws IOException {
// skip
}
- // TODO: parse ISO 6709
- // String locationString = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION);
- // mMuxer.setLocation(Integer.getInteger(rotationString, 0));
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ String locationString = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION);
+ if (locationString != null) {
+ float[] location = new ISO6709LocationParser().parse(locationString);
+ if (location != null) {
+ mMuxer.setLocation(location[0], location[1]);
+ } else {
+ Log.d(TAG, "Failed to parse the location metadata: " + locationString);
+ }
+ }
+ }
try {
mDurationUs = Long.parseLong(mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)) * 1000;
@@ -189,7 +200,7 @@ public void onDetermineOutputFormat() {
mAudioTrackTranscoder.setup();
}
- private void runPipelines() {
+ private void runPipelines() throws InterruptedException {
long loopCount = 0;
if (mDurationUs <= 0) {
double progress = PROGRESS_UNKNOWN;
@@ -208,11 +219,7 @@ private void runPipelines() {
if (mProgressCallback != null) mProgressCallback.onProgress(progress);
}
if (!stepped) {
- try {
- Thread.sleep(SLEEP_TO_WAIT_TRACK_TRANSCODERS);
- } catch (InterruptedException e) {
- // nothing to do
- }
+ Thread.sleep(SLEEP_TO_WAIT_TRACK_TRANSCODERS);
}
}
}
diff --git a/lib/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatStrategyPresets.java b/lib/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatStrategyPresets.java
index 99fc9b90..30802586 100644
--- a/lib/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatStrategyPresets.java
+++ b/lib/src/main/java/net/ypresto/androidtranscoder/format/MediaFormatStrategyPresets.java
@@ -16,6 +16,9 @@
package net.ypresto.androidtranscoder.format;
public class MediaFormatStrategyPresets {
+ public static final int AUDIO_BITRATE_AS_IS = -1;
+ public static final int AUDIO_CHANNELS_AS_IS = -1;
+
/**
* @deprecated Use {@link #createExportPreset960x540Strategy()}.
*/
@@ -45,7 +48,7 @@ public static MediaFormatStrategy createAndroid720pStrategy(int bitrate) {
/**
* Preset based on Nexus 4 camera recording with 720p quality.
* This preset is ensured to work on any Android >=4.3 devices by Android CTS (if codec is available).
- *
+ *
* Note: audio transcoding is experimental feature.
*
* @param bitrate Preferred bitrate for video encoding.
diff --git a/lib/src/main/java/net/ypresto/androidtranscoder/utils/AvcCsdUtils.java b/lib/src/main/java/net/ypresto/androidtranscoder/utils/AvcCsdUtils.java
index d458f66c..049296b9 100644
--- a/lib/src/main/java/net/ypresto/androidtranscoder/utils/AvcCsdUtils.java
+++ b/lib/src/main/java/net/ypresto/androidtranscoder/utils/AvcCsdUtils.java
@@ -29,6 +29,9 @@ public class AvcCsdUtils {
private static final byte[] AVC_START_CODE_4 = {0x00, 0x00, 0x00, 0x01};
// Refer: http://www.cardinalpeak.com/blog/the-h-264-sequence-parameter-set/
private static final byte AVC_SPS_NAL = 103; // 0<<7 + 3<<5 + 7<<0
+ // https://tools.ietf.org/html/rfc6184
+ private static final byte AVC_SPS_NAL_2 = 39; // 0<<7 + 1<<5 + 7<<0
+ private static final byte AVC_SPS_NAL_3 = 71; // 0<<7 + 2<<5 + 7<<0
/**
* @return ByteBuffer contains SPS without NAL header.
@@ -40,9 +43,12 @@ public static ByteBuffer getSpsBuffer(MediaFormat format) {
prefixedSpsBuffer.flip();
skipStartCode(prefixedSpsBuffer);
- if (prefixedSpsBuffer.get() != AVC_SPS_NAL) {
+
+ byte spsNalData = prefixedSpsBuffer.get();
+ if (spsNalData != AVC_SPS_NAL && spsNalData != AVC_SPS_NAL_2 && spsNalData != AVC_SPS_NAL_3) {
throw new IllegalStateException("Got non SPS NAL data.");
}
+
return prefixedSpsBuffer.slice();
}
diff --git a/lib/src/main/java/net/ypresto/androidtranscoder/utils/ISO6709LocationParser.java b/lib/src/main/java/net/ypresto/androidtranscoder/utils/ISO6709LocationParser.java
new file mode 100644
index 00000000..273007d3
--- /dev/null
+++ b/lib/src/main/java/net/ypresto/androidtranscoder/utils/ISO6709LocationParser.java
@@ -0,0 +1,37 @@
+package net.ypresto.androidtranscoder.utils;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class ISO6709LocationParser {
+ private final Pattern pattern;
+
+ public ISO6709LocationParser() {
+ this.pattern = Pattern.compile("([+\\-][0-9.]+)([+\\-][0-9.]+)");
+ }
+
+ /**
+ * This method parses the given string representing a geographic point location by coordinates in ISO 6709 format
+ * and returns the latitude and the longitude in float. If location
is not in ISO 6709 format,
+ * this method returns null
+ *
+ * @param location a String representing a geographic point location by coordinates in ISO 6709 format
+ * @return null
if the given string is not as expected, an array of floats with size 2,
+ * where the first element represents latitude and the second represents longitude, otherwise.
+ */
+ public float[] parse(String location) {
+ if (location == null) return null;
+ Matcher m = pattern.matcher(location);
+ if (m.find() && m.groupCount() == 2) {
+ String latstr = m.group(1);
+ String lonstr = m.group(2);
+ try {
+ float lat = Float.parseFloat(latstr);
+ float lon = Float.parseFloat(lonstr);
+ return new float[]{lat, lon};
+ } catch (NumberFormatException ignored) {
+ }
+ }
+ return null;
+ }
+}
diff --git a/lib/src/main/res/values/strings.xml b/lib/src/main/res/values/strings.xml
deleted file mode 100644
index 85420055..00000000
--- a/lib/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-
-