├── .gitignore
├── COPYING.txt
├── README-libvpx.txt
├── README.md
├── app
├── build.gradle
├── libs
│ └── vorbis-java-1.0.0-beta.jar
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ ├── android
│ │ └── support
│ │ │ └── v4
│ │ │ └── util
│ │ │ └── LruCache.java
│ ├── com
│ │ └── dozingcatsoftware
│ │ │ ├── ebml
│ │ │ ├── EBMLContainerElement.java
│ │ │ ├── EBMLElement.java
│ │ │ ├── EBMLElementType.java
│ │ │ ├── EBMLFileWriter.java
│ │ │ ├── EBMLReader.java
│ │ │ ├── EBMLUtilities.java
│ │ │ └── MatroskaID.java
│ │ │ ├── eyeball
│ │ │ ├── AboutActivity.java
│ │ │ ├── CameraImageProcessor.java
│ │ │ ├── ColorPickerDialog.java
│ │ │ ├── ColorScheme.java
│ │ │ ├── EyeballMain.java
│ │ │ ├── ImageListActivity.java
│ │ │ ├── MediaTabActivity.java
│ │ │ ├── NewPictureJob.java
│ │ │ ├── NewPictureReceiver.java
│ │ │ ├── NewPictureReceiverLegacyBroadcast.java
│ │ │ ├── OverlayView.java
│ │ │ ├── PermissionsChecker.java
│ │ │ ├── ProcessPictureOperation.java
│ │ │ ├── ProcessPictureService.java
│ │ │ ├── RecordingQualityPreference.java
│ │ │ ├── SobelEdgeDetector.java
│ │ │ ├── VideoListActivity.java
│ │ │ ├── VideoPlaybackActivity.java
│ │ │ ├── ViewImageActivity.java
│ │ │ ├── WGPreferences.java
│ │ │ ├── WGUtils.java
│ │ │ └── video
│ │ │ │ ├── AbstractViewMediaActivity.java
│ │ │ │ ├── CombineAudioVideo.java
│ │ │ │ ├── CreateVideoZipFileAsyncTask.java
│ │ │ │ ├── CreateWebmAsyncTask.java
│ │ │ │ ├── ImageRecorder.java
│ │ │ │ ├── MediaDirectory.java
│ │ │ │ ├── MediaProperties.java
│ │ │ │ ├── ProcessVideoTask.java
│ │ │ │ ├── VideoReader.java
│ │ │ │ ├── VideoRecorder.java
│ │ │ │ ├── VorbisEncoder.java
│ │ │ │ └── WebMEncoder.java
│ │ │ └── util
│ │ │ ├── ARManager.java
│ │ │ ├── AndroidUtils.java
│ │ │ ├── AsyncImageLoader.java
│ │ │ ├── CameraPreviewProcessingQueue.java
│ │ │ ├── CameraUtils.java
│ │ │ ├── FrameRateManager.java
│ │ │ ├── ScaledBitmapCache.java
│ │ │ ├── ShutterButton.java
│ │ │ └── SingleItemProcessingQueue.java
│ └── org
│ │ └── openintents
│ │ └── widget
│ │ ├── ColorCircle.java
│ │ ├── ColorSlider.java
│ │ └── OnColorChangedListener.java
│ ├── jni
│ ├── .sobel.c.swp
│ ├── .wg_video.c.swp
│ ├── Android.mk
│ ├── Application.mk
│ ├── libvpx
│ │ ├── AUTHORS
│ │ ├── CHANGELOG
│ │ ├── LICENSE
│ │ ├── PATENTS
│ │ ├── README
│ │ ├── third_party
│ │ │ └── libwebm
│ │ │ │ ├── AUTHORS.TXT
│ │ │ │ ├── Android.mk
│ │ │ │ ├── LICENSE.TXT
│ │ │ │ ├── PATENTS.TXT
│ │ │ │ ├── README.libvpx
│ │ │ │ ├── RELEASE.TXT
│ │ │ │ ├── mkvmuxer.cpp
│ │ │ │ ├── mkvmuxer.hpp
│ │ │ │ ├── mkvmuxertypes.hpp
│ │ │ │ ├── mkvmuxerutil.cpp
│ │ │ │ ├── mkvmuxerutil.hpp
│ │ │ │ ├── mkvparser.cpp
│ │ │ │ ├── mkvparser.hpp
│ │ │ │ ├── mkvreader.cpp
│ │ │ │ ├── mkvreader.hpp
│ │ │ │ ├── mkvwriter.cpp
│ │ │ │ ├── mkvwriter.hpp
│ │ │ │ └── webmids.hpp
│ │ ├── tools_common.h
│ │ ├── vpx
│ │ │ ├── svc_context.h
│ │ │ ├── vp8.h
│ │ │ ├── vp8cx.h
│ │ │ ├── vp8dx.h
│ │ │ ├── vpx_codec.h
│ │ │ ├── vpx_decoder.h
│ │ │ ├── vpx_encoder.h
│ │ │ ├── vpx_frame_buffer.h
│ │ │ ├── vpx_image.h
│ │ │ └── vpx_integer.h
│ │ ├── vpx_config.h
│ │ ├── vpx_ports
│ │ │ └── msvc.h
│ │ ├── webmenc.cc
│ │ ├── webmenc.h
│ │ └── y4minput.h
│ ├── libvpx_prebuilt_libs
│ │ ├── armeabi-v7a
│ │ │ └── libvpx.a
│ │ └── x86
│ │ │ └── libvpx.a
│ ├── sobel.c
│ ├── test_wg_video.c
│ └── wg_video.c
│ └── res
│ ├── drawable-hdpi
│ ├── btn_camera_shutter_holo.png
│ ├── btn_camera_shutter_pressed_holo.png
│ ├── btn_video_shutter_holo.png
│ ├── btn_video_shutter_pressed_holo.png
│ ├── btn_video_shutter_recording_holo.png
│ ├── btn_video_shutter_recording_pressed_holo.png
│ ├── ic_camera_front_white_36dp.png
│ ├── ic_camera_rear_white_36dp.png
│ ├── ic_help_outline_white_36dp.png
│ ├── ic_pause_white_48dp.png
│ ├── ic_photo_camera_white_36dp.png
│ ├── ic_photo_library_white_36dp.png
│ ├── ic_play_arrow_white_48dp.png
│ ├── ic_settings_white_36dp.png
│ ├── ic_skip_next_white_48dp.png
│ ├── ic_skip_previous_white_48dp.png
│ ├── ic_videocam_white_36dp.png
│ ├── icon.png
│ ├── mediatab_bg_selected.xml
│ ├── mediatab_bg_selector.xml
│ └── mediatab_bg_unselected.xml
│ ├── drawable-ldpi
│ └── icon.png
│ ├── drawable-mdpi
│ ├── btn_camera_shutter_holo.png
│ ├── btn_camera_shutter_pressed_holo.png
│ ├── btn_video_shutter_holo.png
│ ├── btn_video_shutter_pressed_holo.png
│ ├── btn_video_shutter_recording_holo.png
│ ├── btn_video_shutter_recording_pressed_holo.png
│ ├── ic_camera_front_white_36dp.png
│ ├── ic_camera_rear_white_36dp.png
│ ├── ic_help_outline_white_36dp.png
│ ├── ic_pause_white_48dp.png
│ ├── ic_photo_camera_white_36dp.png
│ ├── ic_photo_library_white_36dp.png
│ ├── ic_play_arrow_white_48dp.png
│ ├── ic_settings_white_36dp.png
│ ├── ic_skip_next_white_48dp.png
│ ├── ic_skip_previous_white_48dp.png
│ ├── ic_videocam_white_36dp.png
│ └── icon.png
│ ├── drawable-xhdpi
│ ├── btn_camera_shutter_holo.png
│ ├── btn_camera_shutter_pressed_holo.png
│ ├── btn_video_shutter_holo.png
│ ├── btn_video_shutter_pressed_holo.png
│ ├── btn_video_shutter_recording_holo.png
│ ├── btn_video_shutter_recording_pressed_holo.png
│ ├── ic_camera_front_white_36dp.png
│ ├── ic_camera_rear_white_36dp.png
│ ├── ic_help_outline_white_36dp.png
│ ├── ic_pause_white_48dp.png
│ ├── ic_photo_camera_white_36dp.png
│ ├── ic_photo_library_white_36dp.png
│ ├── ic_play_arrow_white_48dp.png
│ ├── ic_settings_white_36dp.png
│ ├── ic_skip_next_white_48dp.png
│ ├── ic_skip_previous_white_48dp.png
│ └── ic_videocam_white_36dp.png
│ ├── drawable-xxxhdpi
│ ├── ic_camera_front_white_36dp.png
│ ├── ic_camera_rear_white_36dp.png
│ ├── ic_help_outline_white_36dp.png
│ ├── ic_pause_white_48dp.png
│ ├── ic_photo_camera_white_36dp.png
│ ├── ic_photo_library_white_36dp.png
│ ├── ic_play_arrow_white_48dp.png
│ ├── ic_settings_white_36dp.png
│ ├── ic_skip_next_white_48dp.png
│ ├── ic_skip_previous_white_48dp.png
│ └── ic_videocam_white_36dp.png
│ ├── layout
│ ├── about.xml
│ ├── colorpicker.xml
│ ├── custom_colors.xml
│ ├── imagegrid.xml
│ ├── imagegrid_cell.xml
│ ├── imagelist.xml
│ ├── imagelist_row.xml
│ ├── main.xml
│ ├── mediatab.xml
│ ├── mediatab_bg.xml
│ ├── overlay_and_controls.xml
│ ├── playback.xml
│ ├── videolist.xml
│ └── videolist_row.xml
│ ├── values-v21
│ └── styles.xml
│ ├── values
│ ├── arrays.xml
│ ├── strings.xml
│ └── styles.xml
│ └── xml
│ └── preferences.xml
├── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .idea/
3 | .gradle/
4 | app/.externalNativeBuild/
5 | app/release/
6 | build/
7 | gen/
8 | local.properties
9 |
10 |
--------------------------------------------------------------------------------
/README-libvpx.txt:
--------------------------------------------------------------------------------
1 | WireGoggles uses the libvpx library to create WebM videos. This repository
2 | includes only the subset of libvpx headers and source needed for the NDK code in
3 | wg_video.c to build. The code needs to link against the full libvpx library,
4 | which is included as a binary for ARM and x86 in jni/libvpx_prebuilt_libs.
5 | Building an Android-compatible library from the WebM sources is somewhat tricky;
6 | this is what I did.
7 |
8 | 0. Prerequisites: I was not able to build libvpx successfully on a Mac, so I
9 | used a Linux VM (64-bit Mint Linux 17.3, but anything reasonably recent should
10 | work). You will need to install the Android NDK and the yasm assember, and
11 | possibly other compiler packages so that you can build for ARM and x86.
12 |
13 | 1. Clone the libvpx repository at https://chromium.googlesource.com/webm/libvpx
14 | (i.e. "git clone https://chromium.googlesource.com/webm/libvpx"). Building from
15 | head should work assuming there are no incompatible API changes, but if you
16 | want to use the exact code that the library in the WireGoggles repository was
17 | built against, check out revision ea48370a500537906d62544ca4ed75301d79e772.
18 |
19 | 2. Anywhere you see [NDK_PATH] in the following commands, replace it
20 | with the full path to the Android NDK directory, for example
21 | "/Users/foo/android/android-ndk-r10e".
22 |
23 | To build the ARM library, cd to libvpx and execute:
24 |
25 | ===============================================================================
26 | ./configure --target=armv7-android-gcc --sdk-path=[NDK_PATH] \
27 | --disable-examples --disable-docs --disable-vp8-decoder --disable-vp9-decoder \
28 | --enable-vp8-encoder --disable-vp9-encoder --disable-vp10 --disable-neon \
29 | --disable-neon-asm --disable-runtime-cpu-detect
30 | ===============================================================================
31 |
32 | Then just execute "make", which if all goes well will produce libvpx.a in the
33 | main directory. This is the static library that goes in
34 | jni/libvpx_prebuilt_libs/armeabi-v7a. Run "make clean" before building x86.
35 |
36 | 3. x86 is trickier because we can't use the default NDK runtime. See
37 | http://stackoverflow.com/questions/28010753/android-ndk-returns-an-error-undefined-reference-to-rand
38 |
39 | Run these commands:
40 |
41 | ===============================================================================
42 | export PATH=[ANDROID_NDK_PATH]/toolchains/x86-4.8/prebuilt/linux-x86_64/bin:$PATH
43 |
44 | ASFLAGS="-D__ANDROID__" CROSS=i686-linux-android- \
45 | LDFLAGS="--sysroot=[NDK_PATH]/platforms/android-9/arch-x86" ./configure \
46 | --target=x86-android-gcc --sdk-path=[NDK_PATH] --disable-examples \
47 | --disable-docs --disable-vp8-decoder --disable-vp9-decoder \
48 | --enable-vp8-encoder --disable-vp9-encoder --disable-vp10 \
49 | --disable-runtime-cpu-detect --disable-mmx --disable-sse --disable-sse2 \
50 | --disable-sse3 --disable-ssse3 --disable-sse4_1 \
51 | --extra-cflags="--sysroot=[NDK_PATH]/platforms/android-9/arch-x86"
52 | ===============================================================================
53 |
54 | Now "make" should produce the libvpx.a static library for x86.
55 |
56 | The configure commands above enable only the VP8 encoder and disble VP9 because
57 | I found that the size and quality difference between them wasn't noticeable, but
58 | VP9 is much slower to encode. If you would like to try VP9 you can change the
59 | flags to --enable-vp9-encoder and set WG_USE_VP9 to 1 in wg_video.c.
60 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WireGoggles
2 |
3 | As of March 2018 WireGoggles is not under active development. I may fix bugs but don't plan to add new features.
4 | [Vector Camera](https://github.com/dozingcat/VectorCamera) is its successor.
5 |
6 | WireGoggles is an Android app that displays a real-time wireframe outline of what your camera is
7 | pointing at. You can choose from several rendering styles and create your own color schemes, and
8 | save pictures and videos.
9 |
10 | This started out several years ago as a way to learn Android development. Much of the code is
11 | old and not well structured, and should not be taken as the best way to do things.
12 |
13 | WireGoggles is released under version 3 of the GPL; see [COPYING](COPYING.txt) for the license text.
14 |
15 | This program is distributed in the hope that it will be useful,
16 | but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | GNU General Public License for more details.
19 |
20 | WireGoggles uses open source code from several third-party projects:
21 |
22 | - libvpx from the [WebM Project](http://www.webmproject.org/license/software/)
23 | - Color picker from [OpenIntents](https://github.com/openintents/openintents/tree/master/colorpicker/ColorPicker)
24 | - vorbis-java encoder from xiph.org
25 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 24
5 | buildToolsVersion "26.0.2"
6 |
7 | defaultConfig {
8 | applicationId "com.dozingcatsoftware.WireGoggles"
9 | minSdkVersion 10
10 | targetSdkVersion 24
11 |
12 | ndk {
13 | moduleName 'libvpx'
14 | abiFilters 'armeabi-v7a', 'x86'
15 | }
16 | }
17 |
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
22 | }
23 | }
24 |
25 | externalNativeBuild {
26 | ndkBuild {
27 | path 'src/main/jni/Android.mk'
28 | }
29 | }
30 | }
31 |
32 | dependencies {
33 | compile files('libs/vorbis-java-1.0.0-beta.jar')
34 | }
35 |
--------------------------------------------------------------------------------
/app/libs/vorbis-java-1.0.0-beta.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dozingcat/WireGoggles/04b753dae81d40e0b4e738c2d63f33f5c685af84/app/libs/vorbis-java-1.0.0-beta.jar
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
16 |
17 |
20 |
23 |
24 |
28 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
43 |
44 |
45 |
48 |
49 |
50 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
86 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dozingcatsoftware/ebml/EBMLContainerElement.java:
--------------------------------------------------------------------------------
1 | package com.dozingcatsoftware.ebml;
2 |
3 | import java.io.DataOutput;
4 | import java.io.IOException;
5 | import java.io.OutputStream;
6 |
7 | public class EBMLContainerElement extends EBMLElement {
8 |
9 | static byte[] UNKNOWN_LENGTH_BYTES = new byte[] {0x01, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF};
10 | long contentSize=-1;
11 |
12 |
13 | public EBMLContainerElement(EBMLElementType elementType) {
14 | super(elementType);
15 | }
16 |
17 | public static EBMLContainerElement createWithHexID(String hexID) {
18 | return new EBMLContainerElement(EBMLElementType.elementTypeForHexString(hexID));
19 | }
20 |
21 | // content size is stored directly in an ivar so getContentSize() is overridden, getTotalSize still works
22 | @Override
23 | public long getContentSize() {
24 | return contentSize;
25 | }
26 |
27 | public void setContentSize(long value) {
28 | contentSize = value;
29 | }
30 |
31 | public boolean isUnknownSize() {
32 | return contentSize < 0;
33 | }
34 |
35 | // overridden to write only ID and length bytes
36 | @Override
37 | public void writeToStream(OutputStream output) throws IOException {
38 | output.write(getElementType().getIDBytes());
39 | if (isUnknownSize()) {
40 | output.write(UNKNOWN_LENGTH_BYTES);
41 | }
42 | else {
43 | output.write(EBMLUtilities.bytesForLength(getContentSize()));
44 | }
45 | }
46 |
47 | // because OutputStream doesn't implement DataOutput (interface used by RandomAccessFile), yay Java
48 | @Override
49 | public void writeToStream(DataOutput output) throws IOException {
50 | output.write(getElementType().getIDBytes());
51 | if (isUnknownSize()) {
52 | output.write(UNKNOWN_LENGTH_BYTES);
53 | }
54 | else {
55 | output.write(EBMLUtilities.bytesForLength(getContentSize()));
56 | }
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dozingcatsoftware/ebml/EBMLElement.java:
--------------------------------------------------------------------------------
1 | package com.dozingcatsoftware.ebml;
2 |
3 | import java.io.DataOutput;
4 | import java.io.IOException;
5 | import java.io.OutputStream;
6 |
7 | public class EBMLElement {
8 |
9 | EBMLElementType elementType;
10 | Object value;
11 |
12 | static byte[] EMPTY_BYTES = new byte[0];
13 |
14 | public EBMLElement(EBMLElementType elementType) {
15 | this.elementType = elementType;
16 | }
17 |
18 | public static EBMLElement createWithHexID(String hexID, Object value) {
19 | EBMLElement element = new EBMLElement(EBMLElementType.elementTypeForHexString(hexID));
20 | element.setValue(value);
21 | return element;
22 | }
23 |
24 | public long getContentSize() {
25 | return dataBytes().length;
26 | }
27 |
28 | public long getTotalSize() {
29 | long contentSize = getContentSize();
30 | return contentSize + EBMLUtilities.numberOfBytesToEncodeLength(contentSize) + elementType.getIDLength();
31 | }
32 |
33 | public Object getValue() {
34 | return value;
35 | }
36 | public void setValue(Object v) {
37 | value = v;
38 | }
39 |
40 | // TODO: add setDataBytes method to convert raw bytes to this element's type
41 |
42 | public EBMLElementType getElementType() {
43 | return elementType;
44 | }
45 |
46 | public byte[] dataBytes() {
47 | if (value==null) return EMPTY_BYTES;
48 | if (value instanceof byte[]) {
49 | return (byte[])value;
50 | }
51 | if (value instanceof Float) {
52 | return EBMLUtilities.dataBytesForFloat((Float)value);
53 | }
54 | if (value instanceof Double) {
55 | return EBMLUtilities.dataBytesForDouble((Double)value);
56 | }
57 | if (value instanceof Number) {
58 | return EBMLUtilities.dataBytesForLong(((Number)value).longValue());
59 | }
60 | if (value instanceof String) {
61 | return ((String)value).getBytes();
62 | }
63 | throw new IllegalArgumentException("Unknown value type:" + value + "(" + value.getClass() + ")");
64 | }
65 |
66 | public void writeToStream(OutputStream output) throws IOException {
67 | output.write(this.elementType.getIDBytes());
68 | byte[] data = this.dataBytes();
69 | output.write(EBMLUtilities.bytesForLength(data.length));
70 | output.write(data);
71 | }
72 |
73 | // because OutputStream doesn't implement DataOutput (interface used by RandomAccessFile), yay Java
74 | public void writeToStream(DataOutput output) throws IOException {
75 | output.write(this.elementType.getIDBytes());
76 | byte[] data = this.dataBytes();
77 | output.write(EBMLUtilities.bytesForLength(data.length));
78 | output.write(data);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dozingcatsoftware/ebml/EBMLElementType.java:
--------------------------------------------------------------------------------
1 | package com.dozingcatsoftware.ebml;
2 |
3 | import java.util.Arrays;
4 | import java.util.HashSet;
5 | import java.util.Set;
6 |
7 | public class EBMLElementType {
8 |
9 | byte[] idBytes;
10 | String idHexString;
11 | String name;
12 |
13 | public enum ValueType {
14 | CONTAINER, STRING, BINARY, UNSIGNED_INTEGER, SIGNED_INTEGER, FLOAT
15 | };
16 | ValueType valueType = ValueType.BINARY;
17 |
18 | // Elements with IDs of 4 bytes are containers, but some with shorter IDs are as well
19 | static Set CONTAINER_IDS = new HashSet(Arrays.asList(
20 | MatroskaID.TrackEntry, MatroskaID.Audio
21 | ));
22 |
23 | // TODO: build map of known element types, with value types
24 | static ValueType valueTypeForID(String hexID) {
25 | if (hexID.length()>=8) return ValueType.CONTAINER;
26 | if (CONTAINER_IDS.contains(hexID.toLowerCase())) return ValueType.CONTAINER;
27 | return ValueType.BINARY;
28 | }
29 |
30 | public static EBMLElementType elementTypeForBytes(byte[] bytes) {
31 | EBMLElementType self = new EBMLElementType();
32 | self.idBytes = bytes.clone();
33 | self.idHexString = EBMLUtilities.bytesToHexString(self.idBytes);
34 | self.valueType = valueTypeForID(self.idHexString);
35 | return self;
36 | }
37 |
38 | public static EBMLElementType elementTypeForHexString(String hexString) {
39 | EBMLElementType self = new EBMLElementType();
40 | self.idHexString = hexString.toUpperCase();
41 | self.idBytes = EBMLUtilities.hexStringToBytes(self.idHexString);
42 | self.valueType = valueTypeForID(self.idHexString);
43 | return self;
44 | }
45 |
46 | public byte[] getIDBytes() {
47 | return idBytes.clone();
48 | }
49 |
50 | public String getIDHexString() {
51 | return idHexString;
52 | }
53 |
54 | public int getIDLength() {
55 | return idBytes.length;
56 | }
57 |
58 | public String getName() {
59 | return name;
60 | }
61 | public void setName(String value) {
62 | name = value;
63 | }
64 |
65 |
66 | public EBMLElement createElement() {
67 | if (valueType==ValueType.CONTAINER) {
68 | return new EBMLContainerElement(this);
69 | }
70 | else {
71 | return new EBMLElement(this);
72 | }
73 | }
74 |
75 | public boolean hexIDMatches(String hexID) {
76 | return (hexID!=null && this.idHexString.equals(hexID.toUpperCase()));
77 | }
78 |
79 | @Override
80 | public boolean equals(Object other) {
81 | if (!(other instanceof EBMLElementType)) return false;
82 | return this.idHexString.equals(((EBMLElementType)other).idHexString);
83 | }
84 |
85 | @Override
86 | public int hashCode() {
87 | return this.idHexString.hashCode();
88 | }
89 |
90 | @Override
91 | public String toString() {
92 | return this.idHexString;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dozingcatsoftware/ebml/EBMLFileWriter.java:
--------------------------------------------------------------------------------
1 | package com.dozingcatsoftware.ebml;
2 |
3 | import java.io.File;
4 | import java.io.FileNotFoundException;
5 | import java.io.IOException;
6 | import java.io.RandomAccessFile;
7 | import java.util.Stack;
8 |
9 | public class EBMLFileWriter {
10 |
11 | // We need a random access file because typically for container elements the content size is initially unknown, so
12 | // we have to write a placeholder value (using the full 8 bytes) and go back and fill it in when we know the size.
13 | RandomAccessFile randomAccessFile;
14 |
15 | // As we write container elements, keep track of the total lengths of their sub-elements. Record the starting position
16 | // so that when the container is closed, we can go back and write the correct length.
17 | static class ContainerStackEntry {
18 | public EBMLContainerElement container;
19 | public long startPosition;
20 | public long contentLength = 0;
21 | }
22 |
23 | Stack containerElementStack = new Stack();
24 |
25 | public EBMLFileWriter(String path) throws FileNotFoundException {
26 | File f = new File(path);
27 | if (f.exists()) f.delete();
28 | randomAccessFile = new RandomAccessFile(f, "rw");
29 | }
30 |
31 | public void writeElement(EBMLElement element) throws IOException {
32 | element.writeToStream(randomAccessFile);
33 | // update parent container with the total length of this element
34 | if (!containerElementStack.empty()) {
35 | ContainerStackEntry parent = containerElementStack.peek();
36 | parent.contentLength += element.getTotalSize();
37 | }
38 | }
39 |
40 | public void writeElement(String hexID, Object value) throws IOException {
41 | writeElement(EBMLElement.createWithHexID(hexID, value));
42 | }
43 |
44 | public void startContainerElement(EBMLContainerElement container) throws IOException {
45 | ContainerStackEntry stackEntry = new ContainerStackEntry();
46 | stackEntry.container = container;
47 | stackEntry.startPosition = randomAccessFile.getFilePointer();
48 | containerElementStack.push(stackEntry);
49 |
50 | container.writeToStream(randomAccessFile);
51 | }
52 |
53 | public void startContainerElement(String hexID) throws IOException {
54 | startContainerElement(EBMLContainerElement.createWithHexID(hexID));
55 | }
56 |
57 | public EBMLContainerElement endContainerElement() throws IOException {
58 | ContainerStackEntry stackEntry = containerElementStack.pop();
59 | // the length should be correct, so go back to the length placeholder and write the correct length
60 | long currentPosition = randomAccessFile.getFilePointer();
61 | // The location to write the length is the saved start position, plus the number of ID bytes, plus 1 for the
62 | // leading 01 length byte (which indicates that the length is stored in the next 7 bytes).
63 | long lengthPosition = stackEntry.startPosition + stackEntry.container.getElementType().getIDLength() + 1;
64 | randomAccessFile.seek(lengthPosition);
65 | // EBMLElementContainer.writeElement always writes 8 bytes for the length as a placeholder, first byte is always 01
66 | byte[] lengthBytes = EBMLUtilities.dataBytesForLong(stackEntry.contentLength, 7);
67 | randomAccessFile.write(lengthBytes);
68 |
69 | randomAccessFile.seek(currentPosition);
70 |
71 | // if there's a parent container of this container, update its content size.
72 | // The total size of this container its content size, plus the length of its ID, plus 8 length bytes
73 | if (!containerElementStack.empty()) {
74 | ContainerStackEntry parent = containerElementStack.peek();
75 | parent.contentLength += stackEntry.contentLength + stackEntry.container.getElementType().getIDLength() + 8;
76 | }
77 |
78 | stackEntry.container.setContentSize(stackEntry.contentLength);
79 | return stackEntry.container;
80 | }
81 |
82 | public void close() throws IOException {
83 | randomAccessFile.close();
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dozingcatsoftware/ebml/EBMLReader.java:
--------------------------------------------------------------------------------
1 | package com.dozingcatsoftware.ebml;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 | import java.util.Stack;
6 |
7 | public class EBMLReader {
8 |
9 | public static interface Delegate {
10 | public void parsedEBMLElement(EBMLElement element);
11 | public void startedEBMLContainerElement(EBMLContainerElement element);
12 | public void finishedEBMLContainerElement(EBMLContainerElement element);
13 | public void finishedEBMLStream();
14 | }
15 |
16 | InputStream input;
17 | long bytesRead;
18 | Delegate delegate;
19 |
20 | static class ContainerStackEntry {
21 | public EBMLContainerElement container;
22 | public long endPosition;
23 | }
24 |
25 | Stack containerStack;
26 |
27 | public EBMLReader(InputStream input, Delegate delegate) {
28 | this.input = input;
29 | this.delegate = delegate;
30 | }
31 |
32 | public void go() {
33 | containerStack = new Stack();
34 |
35 | while (true) {
36 | try {
37 | byte[] idBytes = readElementID();
38 | if (idBytes==null) break;
39 | EBMLElement element = EBMLElementType.elementTypeForBytes(idBytes).createElement();
40 | // capture number of bytes storing the length, so we can increment bytesRead correctly
41 | int[] numLengthBytes = new int[1];
42 | long length = EBMLUtilities.readEncodedLength(input, numLengthBytes);
43 | this.bytesRead += numLengthBytes[0];
44 | if (length<=0) break;
45 | if (element instanceof EBMLContainerElement) {
46 | EBMLContainerElement container = (EBMLContainerElement)element;
47 | container.setContentSize(length);
48 | delegate.startedEBMLContainerElement(container);
49 | // push onto container stack with ending position
50 | ContainerStackEntry stackEntry = new ContainerStackEntry();
51 | stackEntry.container = container;
52 | stackEntry.endPosition = this.bytesRead + container.getContentSize();
53 | containerStack.push(stackEntry);
54 | }
55 | else {
56 | byte[] value = readBytes((int)length);
57 | element.setValue(value);
58 | delegate.parsedEBMLElement(element);
59 | // check for closed containers
60 | while (!containerStack.empty()) {
61 | ContainerStackEntry stackEntry = containerStack.peek();
62 | if (bytesRead >= stackEntry.endPosition) {
63 | containerStack.pop();
64 | delegate.finishedEBMLContainerElement(stackEntry.container);
65 | }
66 | else {
67 | break;
68 | }
69 | }
70 | }
71 | }
72 | catch(Exception ex) {
73 | break;
74 | }
75 | }
76 | delegate.finishedEBMLStream();
77 | }
78 |
79 |
80 | int readByte() throws IOException {
81 | int value = input.read();
82 | if (value!=-1) bytesRead++;
83 | return value;
84 | }
85 |
86 | byte[] readBytes(int numBytes) throws IOException {
87 | byte[] bytes = new byte[numBytes];
88 | int nbytes = 0;
89 | while (nbytes < numBytes) {
90 | int result = this.input.read(bytes, nbytes, bytes.length-nbytes);
91 | if (result==-1) return null;
92 | nbytes += result;
93 | }
94 | this.bytesRead += numBytes;
95 | return bytes;
96 | }
97 |
98 | public byte[] readElementID() {
99 | try {
100 | int first = readByte();
101 | if (first==-1) return null;
102 | if (first >= 0x80) {
103 | // one-byte ID
104 | return new byte[] {(byte)first};
105 | }
106 |
107 | int second = readByte();
108 | if (second==-1) return null;
109 | if (first >= 0x40) {
110 | // two-byte ID
111 | return new byte[] {(byte)first, (byte)second};
112 | }
113 |
114 | int third = readByte();
115 | if (third==-1) return null;
116 | if (first >= 0x20) {
117 | // three-byte ID
118 | return new byte[] {(byte)first, (byte)second, (byte)third};
119 | }
120 |
121 | // must be four bytes
122 | int fourth = readByte();
123 | if (fourth==-1) return null;
124 | return new byte[] {(byte)first, (byte)second, (byte)third, (byte)fourth};
125 | }
126 | catch(Exception ex) {
127 | throw new RuntimeException(ex);
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/app/src/main/java/com/dozingcatsoftware/ebml/EBMLUtilities.java:
--------------------------------------------------------------------------------
1 | package com.dozingcatsoftware.ebml;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 |
6 | public class EBMLUtilities {
7 |
8 | static String HEX_CHARS_STRING = "0123456789ABCDEF";
9 | static char[] HEX_CHARS = HEX_CHARS_STRING.toCharArray();
10 |
11 | public static String bytesToHexString(byte[] bytes) {
12 | StringBuilder sb = new StringBuilder(2*bytes.length);
13 | for(int i=0; i>>4) & 0x0F]);
15 | sb.append(HEX_CHARS[bytes[i] & 0x0F]);
16 | }
17 | return sb.toString();
18 | }
19 |
20 | public static byte[] hexStringToBytes(String hexString) {
21 | if (hexString.length()%2!=0) throw new IllegalArgumentException("Hex string length must be even: " + hexString);
22 | byte[] bytes = new byte[hexString.length() / 2];
23 | char[] ucChars = hexString.toUpperCase().toCharArray();
24 | for(int i=0; i 10000001 = 0x81
38 | * 126 -> 11111110 = 0xFE
39 | * 127 -> 01000000 01111111 = 0x40, 0x7F (can't fit in one byte because EBML reserves length bytes of all 1s)
40 | * 128 -> 01000000 10000000 = 0x40, 0x80
41 | *
42 | */
43 | public static byte[] bytesForLength(long length) {
44 | byte[] bytes = new byte[numberOfBytesToEncodeLength(length)];
45 | // put a leading 1 in the encoded bytes, shifted left by 7*size. This will create the correct number of leading zeros.
46 | long mask = 1L << (7*bytes.length);
47 | long value = mask | length;
48 | // big-endian, least significant bytes at end of array
49 | for(int i=bytes.length-1; i>=0; i--) {
50 | bytes[i] = (byte)(value & 0xFF);
51 | value >>= 8;
52 | }
53 | return bytes;
54 | }
55 |
56 | public static int numberOfBytesToEncodeLength(long length) {
57 | if (length<0) throw new IllegalArgumentException("Length is negative: " + length);
58 | if (length < (1<<7 - 1)) return 1;
59 | if (length < (1<<14 - 1)) return 2;
60 | if (length < (1<<21 - 1)) return 3;
61 | if (length < (1<<28 - 1)) return 4;
62 | if (length < (1<<35 - 1)) return 5;
63 | if (length < (1<<42 - 1)) return 6;
64 | if (length < (1<<49 - 1)) return 7;
65 | if (length < (1<<56 - 1)) return 8;
66 | throw new IllegalArgumentException("Length exceeds maximum: " + length);
67 | }
68 |
69 | // bytesReadOut is optional output parameter that will have the total number of bytes read stored at index 0
70 | public static long readEncodedLength(InputStream input, int[] bytesReadOut) {
71 | try {
72 | int lbyte = input.read();
73 | if (lbyte==-1) return -1;
74 | if (lbyte==0) throw new IllegalStateException("First length byte is 0");
75 | // there will always be 24 extra leading zeros from the first 3 bytes
76 | int nbytes = 1 + Integer.numberOfLeadingZeros(lbyte) - 24;
77 |
78 | // start with the first byte with the leading 1 masked out
79 | long value = lbyte ^ Integer.highestOneBit(lbyte);
80 | // read successive bytes, shifting current value each time (big-endian, most significant bytes first)
81 | for(int i=1; i>>= 8;
108 | }
109 | return bytes;
110 | }
111 |
112 | public static byte[] dataBytesForLong(long value) {
113 | return dataBytesForLong(value, 1);
114 | }
115 |
116 | public static long longForDataBytes(byte[] bytes) {
117 | long value = 0;
118 | for(int i=0; i imageDirectories;
31 | List