/***************************************************************************** * LibVlcUtil.java ***************************************************************************** * Copyright © 2011-2013 VLC authors and VideoLAN * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ package org.videolan.libvlc.util; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Locale; import android.annotation.TargetApi; import android.content.Context; import android.content.pm.ApplicationInfo; import android.net.Uri; import android.os.Build; import android.util.Log; import org.videolan.libvlc.LibVLC; import org.videolan.libvlc.Media; public class VLCUtil { public final static String TAG = "VLC/LibVLC/Util"; private static String errorMsg = null; private static boolean isCompatible = false; public static String getErrorMsg() { return errorMsg; } public static boolean hasCompatibleCPU(Context context) { // If already checked return cached result if (errorMsg != null || isCompatible) return isCompatible; final File lib = searchLibrary(context.getApplicationInfo()); if (lib == null) return true; ElfData elf = readLib(lib); if (elf == null) { Log.e(TAG, "WARNING: Unable to read libvlcjni.so; cannot check device ABI!"); Log.e(TAG, "WARNING: Cannot guarantee correct ABI for this build (may crash)!"); return true; } String CPU_ABI = android.os.Build.CPU_ABI; String CPU_ABI2 = "none"; if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) { // CPU_ABI2 since 2.2 try { CPU_ABI2 = (String) android.os.Build.class.getDeclaredField("CPU_ABI2").get(null); } catch (Exception e) { } } final boolean elfHasX86 = elf.e_machine == EM_386 || elf.e_machine == EM_X86_64; final boolean elfHasArm = elf.e_machine == EM_ARM || elf.e_machine == EM_AARCH64; final boolean elfHasMips = elf.e_machine == EM_MIPS; final boolean elfIs64bits = elf.is64bits; Log.i(TAG, "ELF ABI = " + (elfHasArm ? "arm" : elfHasX86 ? "x86" : "mips") + ", " + (elfIs64bits ? "64bits" : "32bits")); Log.i(TAG, "ELF arch = " + elf.att_arch); Log.i(TAG, "ELF fpu = " + elf.att_fpu); boolean hasNeon = false, hasFpu = false, hasArmV6 = false, hasArmV7 = false, hasMips = false, hasX86 = false, is64bits = false; float bogoMIPS = -1; int processors = 0; if (CPU_ABI.equals("x86") || CPU_ABI2.equals("x86")) { hasX86 = true; } else if (CPU_ABI.equals("x86_64") || CPU_ABI2.equals("x86_64")) { hasX86 = true; is64bits = true; } else if (CPU_ABI.equals("armeabi-v7a") || CPU_ABI2.equals("armeabi-v7a")) { hasArmV7 = true; hasArmV6 = true; /* Armv7 is backwards compatible to < v6 */ } else if (CPU_ABI.equals("armeabi") || CPU_ABI2.equals("armeabi")) { hasArmV6 = true; } else if (CPU_ABI.equals("arm64-v8a") || CPU_ABI2.equals("arm64-v8a")) { hasNeon = true; hasArmV6 = true; hasArmV7 = true; is64bits = true; } FileReader fileReader = null; BufferedReader br = null; try { fileReader = new FileReader("/proc/cpuinfo"); br = new BufferedReader(fileReader); String line; while ((line = br.readLine()) != null) { if (!hasArmV7 && line.contains("AArch64")) { hasArmV7 = true; hasArmV6 = true; /* Armv8 is backwards compatible to < v7 */ } if (!hasArmV7 && line.contains("ARMv7")) { hasArmV7 = true; hasArmV6 = true; /* Armv7 is backwards compatible to < v6 */ } if (!hasArmV7 && !hasArmV6 && line.contains("ARMv6")) hasArmV6 = true; // "clflush size" is a x86-specific cpuinfo tag. // (see kernel sources arch/x86/kernel/cpu/proc.c) if (line.contains("clflush size")) hasX86 = true; if (line.contains("GenuineIntel")) hasX86 = true; // "microsecond timers" is specific to MIPS. // see arch/mips/kernel/proc.c if (line.contains("microsecond timers")) hasMips = true; if (!hasNeon && (line.contains("neon") || line.contains("asimd"))) hasNeon = true; if (!hasFpu && (line.contains("vfp") || (line.contains("Features") && line.contains("fp")))) hasFpu = true; if (line.startsWith("processor")) processors++; if (bogoMIPS < 0 && line.toLowerCase(Locale.ENGLISH).contains("bogomips")) { String[] bogo_parts = line.split(":"); try { bogoMIPS = Float.parseFloat(bogo_parts[1].trim()); } catch (NumberFormatException e) { bogoMIPS = -1; // invalid bogomips } } } } catch (IOException ex) { ex.printStackTrace(); errorMsg = "IOException whilst reading cpuinfo flags"; isCompatible = false; return false; } finally { if (br != null) { try { br.close(); } catch (IOException e) { } } if (fileReader != null) { try { fileReader.close(); } catch (IOException e) { } } } if (processors == 0) processors = 1; // possibly borked cpuinfo? // Enforce proper architecture to prevent problems if (elfHasX86 && !hasX86) { errorMsg = "x86 build on non-x86 device"; isCompatible = false; return false; } else if (elfHasArm && hasX86) { errorMsg = "ARM build on x86 device"; isCompatible = false; return false; } if (elfHasMips && !hasMips) { errorMsg = "MIPS build on non-MIPS device"; isCompatible = false; return false; } else if (elfHasArm && hasMips) { errorMsg = "ARM build on MIPS device"; isCompatible = false; return false; } if (elf.e_machine == EM_ARM && elf.att_arch.startsWith("v7") && !hasArmV7) { errorMsg = "ARMv7 build on non-ARMv7 device"; isCompatible = false; return false; } if (elf.e_machine == EM_ARM) { if (elf.att_arch.startsWith("v6") && !hasArmV6) { errorMsg = "ARMv6 build on non-ARMv6 device"; isCompatible = false; return false; } else if (elf.att_fpu && !hasFpu) { errorMsg = "FPU-enabled build on non-FPU device"; isCompatible = false; return false; } } if (elfIs64bits && !is64bits) { errorMsg = "64bits build on 32bits device"; isCompatible = false; } float frequency = -1; fileReader = null; br = null; String line = ""; try { fileReader = new FileReader("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq"); br = new BufferedReader(fileReader); line = br.readLine(); if (line != null) frequency = Float.parseFloat(line) / 1000.f; /* Convert to MHz */ } catch (IOException ex) { Log.w(TAG, "Could not find maximum CPU frequency!"); } catch (NumberFormatException e) { Log.w(TAG, "Could not parse maximum CPU frequency!"); Log.w(TAG, "Failed to parse: " + line); } finally { if (br != null) { try { br.close(); } catch (IOException e) { } } if (fileReader != null) { try { fileReader.close(); } catch (IOException e) { } } } errorMsg = null; isCompatible = true; // Store into MachineSpecs machineSpecs = new MachineSpecs(); machineSpecs.hasArmV6 = hasArmV6; machineSpecs.hasArmV7 = hasArmV7; machineSpecs.hasFpu = hasFpu; machineSpecs.hasMips = hasMips; machineSpecs.hasNeon = hasNeon; machineSpecs.hasX86 = hasX86; machineSpecs.is64bits = is64bits; machineSpecs.bogoMIPS = bogoMIPS; machineSpecs.processors = processors; machineSpecs.frequency = frequency; return true; } public static MachineSpecs getMachineSpecs() { return machineSpecs; } private static MachineSpecs machineSpecs = null; public static class MachineSpecs { public boolean hasNeon; public boolean hasFpu; public boolean hasArmV6; public boolean hasArmV7; public boolean hasMips; public boolean hasX86; public boolean is64bits; public float bogoMIPS; public int processors; public float frequency; /* in MHz */ } private static final int EM_386 = 3; private static final int EM_MIPS = 8; private static final int EM_ARM = 40; private static final int EM_X86_64 = 62; private static final int EM_AARCH64 = 183; private static final int ELF_HEADER_SIZE = 52; private static final int SECTION_HEADER_SIZE = 40; private static final int SHT_ARM_ATTRIBUTES = 0x70000003; private static class ElfData { ByteOrder order; boolean is64bits; int e_machine; int e_shoff; int e_shnum; int sh_offset; int sh_size; String att_arch; boolean att_fpu; } @TargetApi(Build.VERSION_CODES.GINGERBREAD) private static File searchLibrary(ApplicationInfo applicationInfo) { // Search for library path String[] libraryPaths; if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { final String property = System.getProperty("java.library.path"); libraryPaths = property.split(":"); } else { libraryPaths = new String[1]; if (AndroidUtil.isGingerbreadOrLater()) libraryPaths[0] = applicationInfo.nativeLibraryDir; else libraryPaths[0] = applicationInfo.dataDir + "/lib"; } if (libraryPaths[0] == null) { Log.e(TAG, "can't find library path"); return null; } // Search for libvlcjni.so File lib; for (String libraryPath : libraryPaths) { lib = new File(libraryPath, "libvlcjni.so"); if (lib.exists() && lib.canRead()) return lib; } Log.e(TAG, "WARNING: Can't find shared library"); return null; } /** '*' prefix means it's unsupported */ private final static String[] CPU_archs = {"*Pre-v4", "*v4", "*v4T", "v5T", "v5TE", "v5TEJ", "v6", "v6KZ", "v6T2", "v6K", "v7", "*v6-M", "*v6S-M", "*v7E-M", "*v8"}; private static ElfData readLib(File file) { RandomAccessFile in = null; try { in = new RandomAccessFile(file, "r"); ElfData elf = new ElfData(); if (!readHeader(in, elf)) return null; switch (elf.e_machine) { case EM_386: case EM_MIPS: case EM_X86_64: case EM_AARCH64: return elf; case EM_ARM: in.close(); in = new RandomAccessFile(file, "r"); if (!readSection(in, elf)) return null; in.close(); in = new RandomAccessFile(file, "r"); if (!readArmAttributes(in, elf)) return null; break; default: return null; } return elf; } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (in != null) in.close(); } catch (IOException e) { } } return null; } private static boolean readHeader(RandomAccessFile in, ElfData elf) throws IOException { // http://www.sco.com/developers/gabi/1998-04-29/ch4.eheader.html byte[] bytes = new byte[ELF_HEADER_SIZE]; in.readFully(bytes); if (bytes[0] != 127 || bytes[1] != 'E' || bytes[2] != 'L' || bytes[3] != 'F' || (bytes[4] != 1 && bytes[4] != 2)) { Log.e(TAG, "ELF header invalid"); return false; } elf.is64bits = bytes[4] == 2; elf.order = bytes[5] == 1 ? ByteOrder.LITTLE_ENDIAN // ELFDATA2LSB : ByteOrder.BIG_ENDIAN; // ELFDATA2MSB // wrap bytes in a ByteBuffer to force endianess ByteBuffer buffer = ByteBuffer.wrap(bytes); buffer.order(elf.order); elf.e_machine = buffer.getShort(18); /* Architecture */ elf.e_shoff = buffer.getInt(32); /* Section header table file offset */ elf.e_shnum = buffer.getShort(48); /* Section header table entry count */ return true; } private static boolean readSection(RandomAccessFile in, ElfData elf) throws IOException { byte[] bytes = new byte[SECTION_HEADER_SIZE]; in.seek(elf.e_shoff); for (int i = 0; i < elf.e_shnum; ++i) { in.readFully(bytes); // wrap bytes in a ByteBuffer to force endianess ByteBuffer buffer = ByteBuffer.wrap(bytes); buffer.order(elf.order); int sh_type = buffer.getInt(4); /* Section type */ if (sh_type != SHT_ARM_ATTRIBUTES) continue; elf.sh_offset = buffer.getInt(16); /* Section file offset */ elf.sh_size = buffer.getInt(20); /* Section size in bytes */ return true; } return false; } private static boolean readArmAttributes(RandomAccessFile in, ElfData elf) throws IOException { byte[] bytes = new byte[elf.sh_size]; in.seek(elf.sh_offset); in.readFully(bytes); // wrap bytes in a ByteBuffer to force endianess ByteBuffer buffer = ByteBuffer.wrap(bytes); buffer.order(elf.order); //http://infocenter.arm.com/help/topic/com.arm.doc.ihi0044e/IHI0044E_aaelf.pdf //http://infocenter.arm.com/help/topic/com.arm.doc.ihi0045d/IHI0045D_ABI_addenda.pdf if (buffer.get() != 'A') // format-version return false; // sub-sections loop while (buffer.remaining() > 0) { int start_section = buffer.position(); int length = buffer.getInt(); String vendor = getString(buffer); if (vendor.equals("aeabi")) { // tags loop while (buffer.position() < start_section + length) { int start = buffer.position(); int tag = buffer.get(); int size = buffer.getInt(); // skip if not Tag_File, we don't care about others if (tag != 1) { buffer.position(start + size); continue; } // attributes loop while (buffer.position() < start + size) { tag = getUleb128(buffer); if (tag == 6) { // CPU_arch int arch = getUleb128(buffer); elf.att_arch = CPU_archs[arch]; } else if (tag == 27) { // ABI_HardFP_use getUleb128(buffer); elf.att_fpu = true; } else { // string for 4=CPU_raw_name / 5=CPU_name / 32=compatibility // string for >32 && odd tags // uleb128 for other tag %= 128; if (tag == 4 || tag == 5 || tag == 32 || (tag > 32 && (tag & 1) != 0)) getString(buffer); else getUleb128(buffer); } } } break; } } return true; } private static String getString(ByteBuffer buffer) { StringBuilder sb = new StringBuilder(buffer.limit()); while (buffer.remaining() > 0) { char c = (char) buffer.get(); if (c == 0) break; sb.append(c); } return sb.toString(); } private static int getUleb128(ByteBuffer buffer) { int ret = 0; int c; do { ret <<= 7; c = buffer.get(); ret |= c & 0x7f; } while ((c & 0x80) > 0); return ret; } /** * Get a media thumbnail. * @return a bytearray with the RGBA thumbnail data inside. */ public static byte[] getThumbnail(LibVLC libVLC, Uri uri, int i_width, int i_height) { /* dvd thumbnails can work only with dvdsimple demux */ if (uri.getLastPathSegment().endsWith(".iso")) uri = Uri.parse("dvdsimple://" + uri.getEncodedPath()); final Media media = new Media(libVLC, uri); byte[] bytes = getThumbnail(media, i_width, i_height); media.release(); return bytes; } public static byte[] getThumbnail(Media media, int i_width, int i_height) { media.addOption(":no-audio"); media.addOption(":no-spu"); media.addOption(":no-osd"); return nativeGetThumbnail(media, i_width, i_height); } private static native byte[] nativeGetThumbnail(Media media, int i_width, int i_height); }