1 /* 2 * Copyright (C) 2019 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 com.android.providers.media.util; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.media.ExifInterface; 22 import android.system.ErrnoException; 23 import android.system.Os; 24 import android.system.OsConstants; 25 import android.util.Log; 26 import android.util.LongArray; 27 28 import libcore.io.IoBridge; 29 import libcore.io.Memory; 30 31 import java.io.EOFException; 32 import java.io.File; 33 import java.io.FileDescriptor; 34 import java.io.FileInputStream; 35 import java.io.IOException; 36 import java.nio.ByteOrder; 37 import java.util.ArrayList; 38 import java.util.LinkedList; 39 import java.util.List; 40 import java.util.Objects; 41 import java.util.Queue; 42 import java.util.UUID; 43 44 /** 45 * Simple parser for ISO base media file format. Designed to mirror ergonomics 46 * of {@link ExifInterface}. 47 */ 48 public class IsoInterface { 49 private static final String TAG = "IsoInterface"; 50 private static final boolean LOGV = Log.isLoggable(TAG, Log.VERBOSE); 51 52 public static final int BOX_FTYP = 0x66747970; 53 public static final int BOX_UUID = 0x75756964; 54 public static final int BOX_META = 0x6d657461; 55 public static final int BOX_XMP = 0x584d505f; 56 57 public static final int BOX_LOCI = 0x6c6f6369; 58 public static final int BOX_XYZ = 0xa978797a; 59 public static final int BOX_GPS = 0x67707320; 60 public static final int BOX_GPS0 = 0x67707330; 61 62 /** 63 * Test if given box type is a well-known parent box type. 64 */ isBoxParent(int type)65 private static boolean isBoxParent(int type) { 66 switch (type) { 67 case 0x6d6f6f76: // moov 68 case 0x6d6f6f66: // moof 69 case 0x74726166: // traf 70 case 0x6d667261: // mfra 71 case 0x7472616b: // trak 72 case 0x74726566: // tref 73 case 0x6d646961: // mdia 74 case 0x6d696e66: // minf 75 case 0x64696e66: // dinf 76 case 0x7374626c: // stbl 77 case 0x65647473: // edts 78 case 0x75647461: // udta 79 case 0x6970726f: // ipro 80 case 0x73696e66: // sinf 81 case 0x686e7469: // hnti 82 case 0x68696e66: // hinf 83 case 0x6a703268: // jp2h 84 case 0x696c7374: // ilst 85 case 0x6d657461: // meta 86 return true; 87 default: 88 return false; 89 } 90 } 91 92 /** Top-level boxes */ 93 private List<Box> mRoots = new ArrayList<>(); 94 /** Flattened view of all boxes */ 95 private List<Box> mFlattened = new ArrayList<>(); 96 97 private static class Box { 98 public final int type; 99 public final long[] range; 100 public UUID uuid; 101 public byte[] data; 102 public List<Box> children; 103 Box(int type, long[] range)104 public Box(int type, long[] range) { 105 this.type = type; 106 this.range = range; 107 } 108 } 109 typeToString(int type)110 private static String typeToString(int type) { 111 final byte[] buf = new byte[4]; 112 Memory.pokeInt(buf, 0, type, ByteOrder.BIG_ENDIAN); 113 return new String(buf); 114 } 115 readInt(@onNull FileDescriptor fd)116 private static int readInt(@NonNull FileDescriptor fd) 117 throws ErrnoException, IOException { 118 final byte[] buf = new byte[4]; 119 if (Os.read(fd, buf, 0, 4) == 4) { 120 return Memory.peekInt(buf, 0, ByteOrder.BIG_ENDIAN); 121 } else { 122 throw new EOFException(); 123 } 124 } 125 readUuid(@onNull FileDescriptor fd)126 private static @NonNull UUID readUuid(@NonNull FileDescriptor fd) 127 throws ErrnoException, IOException { 128 final long high = (((long) readInt(fd)) << 32L) | ((long) readInt(fd)) & 0xffffffffL; 129 final long low = (((long) readInt(fd)) << 32L) | ((long) readInt(fd)) & 0xffffffffL; 130 return new UUID(high, low); 131 } 132 parseNextBox(@onNull FileDescriptor fd, long end, @NonNull String prefix)133 private static @Nullable Box parseNextBox(@NonNull FileDescriptor fd, long end, 134 @NonNull String prefix) throws ErrnoException, IOException { 135 final long pos = Os.lseek(fd, 0, OsConstants.SEEK_CUR); 136 if (pos == end) { 137 return null; 138 } 139 140 final long len = Integer.toUnsignedLong(readInt(fd)); 141 if (len <= 0 || pos + len > end) { 142 Log.w(TAG, "Invalid box at " + pos + " of length " + len 143 + " reached beyond end of parent " + end); 144 return null; 145 } 146 147 // Skip past legacy data on 'meta' box 148 final int type = readInt(fd); 149 if (type == BOX_META) { 150 readInt(fd); 151 } 152 153 final Box box = new Box(type, new long[] { pos, len }); 154 if (LOGV) { 155 Log.v(TAG, prefix + "Found box " + typeToString(type) 156 + " at " + pos + " length " + len); 157 } 158 159 // Parse UUID box 160 if (type == BOX_UUID) { 161 box.uuid = readUuid(fd); 162 if (LOGV) { 163 Log.v(TAG, prefix + " UUID " + box.uuid); 164 } 165 166 box.data = new byte[(int) (len - 8 - 16)]; 167 IoBridge.read(fd, box.data, 0, box.data.length); 168 } 169 170 // Parse XMP box 171 if (type == BOX_XMP) { 172 box.data = new byte[(int) (len - 8)]; 173 IoBridge.read(fd, box.data, 0, box.data.length); 174 } 175 176 // Recursively parse any children boxes 177 if (isBoxParent(type)) { 178 box.children = new ArrayList<>(); 179 180 Box child; 181 while ((child = parseNextBox(fd, pos + len, prefix + " ")) != null) { 182 box.children.add(child); 183 } 184 } 185 186 // Skip completely over ourselves 187 Os.lseek(fd, pos + len, OsConstants.SEEK_SET); 188 return box; 189 } 190 IsoInterface(@onNull FileDescriptor fd)191 private IsoInterface(@NonNull FileDescriptor fd) throws IOException { 192 try { 193 Os.lseek(fd, 4, OsConstants.SEEK_SET); 194 if (readInt(fd) != BOX_FTYP) { 195 if (LOGV) { 196 Log.w(TAG, "Missing 'ftyp' header"); 197 } 198 return; 199 } 200 201 final long end = Os.lseek(fd, 0, OsConstants.SEEK_END); 202 Os.lseek(fd, 0, OsConstants.SEEK_SET); 203 Box box; 204 while ((box = parseNextBox(fd, end, "")) != null) { 205 mRoots.add(box); 206 } 207 } catch (ErrnoException e) { 208 throw e.rethrowAsIOException(); 209 } 210 211 // Also create a flattened structure to speed up searching 212 final Queue<Box> queue = new LinkedList<>(mRoots); 213 while (!queue.isEmpty()) { 214 final Box box = queue.poll(); 215 mFlattened.add(box); 216 if (box.children != null) { 217 queue.addAll(box.children); 218 } 219 } 220 } 221 fromFile(@onNull File file)222 public static @NonNull IsoInterface fromFile(@NonNull File file) 223 throws IOException { 224 try (FileInputStream is = new FileInputStream(file)) { 225 return fromFileDescriptor(is.getFD()); 226 } 227 } 228 fromFileDescriptor(@onNull FileDescriptor fd)229 public static @NonNull IsoInterface fromFileDescriptor(@NonNull FileDescriptor fd) 230 throws IOException { 231 return new IsoInterface(fd); 232 } 233 234 /** 235 * Return a list of content ranges of all boxes of requested type. 236 * <p> 237 * This is always an array of even length, and all values are in exact file 238 * positions (no relative values). 239 */ getBoxRanges(int type)240 public @NonNull long[] getBoxRanges(int type) { 241 LongArray res = new LongArray(); 242 for (Box box : mFlattened) { 243 if (box.type == type) { 244 res.add(box.range[0] + 8); 245 res.add(box.range[0] + box.range[1]); 246 } 247 } 248 return res.toArray(); 249 } 250 getBoxRanges(@onNull UUID uuid)251 public @NonNull long[] getBoxRanges(@NonNull UUID uuid) { 252 LongArray res = new LongArray(); 253 for (Box box : mFlattened) { 254 if (box.type == BOX_UUID && Objects.equals(box.uuid, uuid)) { 255 res.add(box.range[0] + 8 + 16); 256 res.add(box.range[0] + box.range[1]); 257 } 258 } 259 return res.toArray(); 260 } 261 262 /** 263 * Return contents of the first box of requested type. 264 */ getBoxBytes(int type)265 public @Nullable byte[] getBoxBytes(int type) { 266 for (Box box : mFlattened) { 267 if (box.type == type) { 268 return box.data; 269 } 270 } 271 return null; 272 } 273 274 /** 275 * Return contents of the first UUID box of requested type. 276 */ getBoxBytes(@onNull UUID uuid)277 public @Nullable byte[] getBoxBytes(@NonNull UUID uuid) { 278 for (Box box : mFlattened) { 279 if (box.type == BOX_UUID && Objects.equals(box.uuid, uuid)) { 280 return box.data; 281 } 282 } 283 return null; 284 } 285 } 286