/*
* Copyright (C) 2008 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* 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.
*/
package com.ringdroid.soundfile;
import java.io.File;
import java.io.FileInputStream;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.HashMap;
/**
* CheapSoundFile is the parent class of several subclasses that each
* do a "cheap" scan of various sound file formats, parsing as little
* as possible in order to understand the high-level frame structure
* and get a rough estimate of the volume level of each frame. Each
* subclass is able to:
* - open a sound file
* - return the sample rate and number of frames
* - return an approximation of the volume level of each frame
* - write a new sound file with a subset of the frames
*
* A frame should represent no less than 1 ms and no more than 100 ms of
* audio. This is compatible with the native frame sizes of most audio
* file formats already, but if not, this class should expose virtual
* frames in that size range.
*/
public class CheapSoundFile {
public interface ProgressListener {
/**
* Will be called by the CheapSoundFile subclass periodically
* with values between 0.0 and 1.0. Return true to continue
* loading the file, and false to cancel.
*/
boolean reportProgress(double fractionComplete);
}
public interface Factory {
public CheapSoundFile create();
public String[] getSupportedExtensions();
}
static Factory[] sSubclassFactories = new Factory[] {
CheapAAC.getFactory(),
CheapAMR.getFactory(),
CheapMP3.getFactory(),
CheapWAV.getFactory(),
};
static ArrayList<String> sSupportedExtensions = new ArrayList<String>();
static HashMap<String, Factory> sExtensionMap =
new HashMap<String, Factory>();
static {
for (Factory f : sSubclassFactories) {
for (String extension : f.getSupportedExtensions()) {
sSupportedExtensions.add(extension);
sExtensionMap.put(extension, f);
}
}
}
/**
* Static method to create the appropriate CheapSoundFile subclass
* given a filename.
*
* TODO: make this more modular rather than hardcoding the logic
*/
public static CheapSoundFile create(String fileName,
ProgressListener progressListener)
throws java.io.FileNotFoundException,
java.io.IOException {
File f = new File(fileName);
if (!f.exists()) {
throw new java.io.FileNotFoundException(fileName);
}
String name = f.getName().toLowerCase();
String[] components = name.split("\\.");
if (components.length < 2) {
return null;
}
Factory factory = sExtensionMap.get(components[components.length - 1]);
if (factory == null) {
return null;
}
CheapSoundFile soundFile = factory.create();
soundFile.setProgressListener(progressListener);
soundFile.ReadFile(f);
return soundFile;
}
public static boolean isFilenameSupported(String filename) {
String[] components = filename.toLowerCase().split("\\.");
if (components.length < 2) {
return false;
}
return sExtensionMap.containsKey(components[components.length - 1]);
}
/**
* Return the filename extensions that are recognized by one of
* our subclasses.
*/
public static String[] getSupportedExtensions() {
return sSupportedExtensions.toArray(
new String[sSupportedExtensions.size()]);
}
protected ProgressListener mProgressListener = null;
protected File mInputFile = null;
protected CheapSoundFile() {
}
public void ReadFile(File inputFile)
throws java.io.FileNotFoundException,
java.io.IOException {
mInputFile = inputFile;
}
public void setProgressListener(ProgressListener progressListener) {
mProgressListener = progressListener;
}
public int getNumFrames() {
return 0;
}
public int getSamplesPerFrame() {
return 0;
}
public int[] getFrameOffsets() {
return null;
}
public int[] getFrameLens() {
return null;
}
public int[] getFrameGains() {
return null;
}
public int getFileSizeBytes() {
return 0;
}
public int getAvgBitrateKbps() {
return 0;
}
public int getSampleRate() {
return 0;
}
public int getChannels() {
return 0;
}
public String getFiletype() {
return "Unknown";
}
/**
* If and only if this particular file format supports seeking
* directly into the middle of the file without reading the rest of
* the header, this returns the byte offset of the given frame,
* otherwise returns -1.
*/
public int getSeekableFrameOffset(int frame) {
return -1;
}
private static final char[] HEX_CHARS = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
public static String bytesToHex (byte hash[]) {
char buf[] = new char[hash.length * 2];
for (int i = 0, x = 0; i < hash.length; i++) {
buf[x++] = HEX_CHARS[(hash[i] >>> 4) & 0xf];
buf[x++] = HEX_CHARS[hash[i] & 0xf];
}
return new String(buf);
}
public String computeMd5OfFirst10Frames()
throws java.io.FileNotFoundException,
java.io.IOException,
java.security.NoSuchAlgorithmException {
int[] frameOffsets = getFrameOffsets();
int[] frameLens = getFrameLens();
int numFrames = frameLens.length;
if (numFrames > 10) {
numFrames = 10;
}
MessageDigest digest = java.security.MessageDigest.getInstance("MD5");
FileInputStream in = new FileInputStream(mInputFile);
int pos = 0;
for (int i = 0; i < numFrames; i++) {
int skip = frameOffsets[i] - pos;
int len = frameLens[i];
if (skip > 0) {
in.skip(skip);
pos += skip;
}
byte[] buffer = new byte[len];
in.read(buffer, 0, len);
digest.update(buffer);
pos += len;
}
in.close();
byte[] hash = digest.digest();
return bytesToHex(hash);
}
public void WriteFile(File outputFile, int startFrame, int numFrames)
throws java.io.IOException {
}
};