一、布局 main.xml
file_save.xml
MainActivity
package com.hwtt.ringdemo;import java.io.File;import java.io.FileNotFoundException;import java.io.IOException;import java.io.InputStream;import java.io.RandomAccessFile;import java.text.Format;import java.text.SimpleDateFormat;import android.app.Activity;import android.app.AlertDialog;import android.app.ProgressDialog;import android.app.AlertDialog.Builder;import android.content.ContentValues;import android.content.DialogInterface;import android.content.Intent;import android.media.MediaPlayer;import android.net.Uri;import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.provider.MediaStore;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;import android.widget.ImageView;import android.widget.SeekBar;import android.widget.TextView;import android.widget.Toast;import android.widget.SeekBar.OnSeekBarChangeListener;import com.hwtt.soundfile.CheapSoundFile;import com.hwtt.utils.FileSaveDialog;import com.hwtt.utils.FileUtils;public class RingdemoActivity extends Activity { /** Called when the activity is first created. */ private ImageView miniStart;// 播放键 private MediaPlayer mediaPlayer; private LyricView lyricView; // 显示歌词 private SeekBar seekBar; // 进度条 private int INTERVAL = 45;// 歌词每行的间隔 private File mFile; private CheapSoundFile mSoundFile; private TextView startTime, endTime; private Button startBtn, endBtn; private ImageView save; private int mNewFileKind; private String mExtension; private ProgressDialog mProgressDialog; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mediaPlayer = new MediaPlayer(); playMusic(R.raw.smile); mFile = saveToneToSD(getResources().openRawResource(R.raw.smile), "voa"); LoadToneFromSD(); addView(); init(); setListeners(); new Thread(new runable()).start(); } /** * 初始化控件 */ private void addView() { // TODO Auto-generated method stub miniStart = (ImageView) findViewById(R.id.media_start); lyricView = (LyricView) findViewById(R.id.mylrc); seekBar = (SeekBar) findViewById(R.id.ring_seekbar); startTime = (TextView) findViewById(R.id.starttime); endTime = (TextView) findViewById(R.id.endtime); startBtn = (Button) findViewById(R.id.startbtn); endBtn = (Button) findViewById(R.id.endbtn); save = (ImageView) findViewById(R.id.save_img); save.setOnClickListener(new MyClickListener()); startBtn.setOnClickListener(new MyClickListener()); endBtn.setOnClickListener(new MyClickListener()); } /** * 初始化歌词 */ private void init() { SerchLrc(); } private void setListeners() { miniStart.setOnClickListener(new MyClickListener()); seekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { @Override public void onStopTrackingTouch(SeekBar seekBar) { // TODO Auto-generated method stub } @Override public void onStartTrackingTouch(SeekBar seekBar) { // TODO Auto-generated method stub } @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { // TODO Auto-generated method stub if (fromUser) { mediaPlayer.seekTo(progress); lyricView.setOffsetY(220 - lyricView.SelectIndex(progress) * (lyricView.getSIZEWORD() + INTERVAL - 1)); } endTime.setText(mediaPlayer.getCurrentPosition() + ""); } });// mediaPlayer// .setOnCompletionListener(new MediaPlayer.OnCompletionListener() {// @Override// public void onCompletion(MediaPlayer mp) {// lyricView.SetTextSize();// lyricView.setOffsetY(200);// //mediaPlayer.start();// }// }); } public void SerchLrc() { LyricView.setBlLrc(true); LyricView.read(); lyricView.SetTextSize(); lyricView.setOffsetY(350); } class MyClickListener implements OnClickListener { @Override public void onClick(View v) { // TODO Auto-generated method stub switch (v.getId()) { // 开始播放按钮监听 case R.id.media_start: if (mediaPlayer.isPlaying()) { /* 暂停 */ mediaPlayer.pause(); miniStart.setImageResource(R.drawable.mini_pause_button); } else { /* 开始播放 */ mediaPlayer.start(); miniStart.setImageResource(R.drawable.mini_play_button); lyricView.setOffsetY(220 - lyricView.SelectIndex(mediaPlayer .getCurrentPosition()) * (lyricView.getSIZEWORD() + INTERVAL - 1)); seekBar.setMax(mediaPlayer.getDuration()); } break; case R.id.save_img: toSaveTone(); break; case R.id.startbtn: if (mediaPlayer != null && mediaPlayer.isPlaying()) { startTime.setText(mediaPlayer.getCurrentPosition() + ""); } break; case R.id.endbtn: endTime.setText(mediaPlayer.getCurrentPosition() + ""); if (mediaPlayer != null && mediaPlayer.isPlaying()) { mediaPlayer.pause(); miniStart.setImageResource(R.drawable.mini_pause_button); } break; default: break; } } private void toSaveTone() { // TODO Auto-generated method stub if (mediaPlayer != null && mediaPlayer.isPlaying()) { mediaPlayer.pause(); miniStart.setImageResource(R.drawable.mini_pause_button); } final Handler handler = new Handler() { public void handleMessage(Message response) { CharSequence newTitle = (CharSequence) response.obj; mNewFileKind = response.arg1; saveRingtone(newTitle); } }; Message message = Message.obtain(handler); FileSaveDialog dlog = new FileSaveDialog(RingdemoActivity.this, getResources(), "ring", message); dlog.show(); } } protected void saveRingtone(final CharSequence title) { // TODO Auto-generated method stub final String outPath = makeRingtoneFilename(title, mExtension); System.out.println("outPath-->" + outPath); Double endtime = 0.0; Double starttime = 0.0; if (!startTime.getText().toString().equals("00:00")) { starttime = Double.valueOf(startTime.getText().toString()) / 1000; } if(endTime.getText().toString().equals("00:00")){ Toast.makeText(RingdemoActivity.this, "结束时间不能为0", 1).show(); return; } endtime = Double.valueOf(endTime.getText().toString()) / 1000; System.out.println("endtime-->"+endtime); System.out.println("starttime-->"+starttime); final int startFrame = secondsToFrames(starttime); final int endFrame = secondsToFrames(endtime); final int duration = (int) (endtime - starttime + 0.5); System.out.println("mSoundFile.getSampleRate-->" + mSoundFile.getSampleRate()); System.out.println("mSoundFile.mSamplesPerFrame-->" + mSoundFile.getSamplesPerFrame()); System.out.println("开始时间:" + startFrame + " 结束时间:" + endFrame + " 总时间:" + duration); mProgressDialog = new ProgressDialog(this); mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); mProgressDialog.setTitle(R.string.progress_dialog_saving); mProgressDialog.setIndeterminate(true); mProgressDialog.setCancelable(false); mProgressDialog.show(); new Thread() { public void run() { final File outFile = new File(outPath); try { mSoundFile.WriteFile(outFile, startFrame, endFrame - startFrame); // Try to load the new file to make sure it worked final CheapSoundFile.ProgressListener listener = new CheapSoundFile.ProgressListener() { public boolean reportProgress(double frac) { // Do nothing - we're not going to try to // estimate when reloading a saved sound // since it's usually fast, but hard to // estimate anyway. // System.out.println("listener-->rt__true"); return true; // Keep going } }; CheapSoundFile.create(outPath, listener); System.out.println("create-->ok"); } catch (Exception e) { // TODO: handle exception } mProgressDialog.dismiss(); Runnable runnable = new Runnable() { public void run() { System.out.println("title-->" + title); System.out.println("outPath-->" + outPath); System.out.println("outFile-->" + outFile); System.out.println("duration-->" + duration); Toast.makeText(RingdemoActivity.this, "保存在:"+outPath, 1).show(); afterSavingRingtone(title, outPath, outFile, duration); } }; mHandler.post(runnable); } }.start(); } private void afterSavingRingtone(CharSequence title, final String outPath, File outFile, int duration) { // TODO Auto-generated method stub long length = outFile.length(); if (length <= 512) { outFile.delete(); new AlertDialog.Builder(this) .setTitle(R.string.alert_title_failure).setMessage( R.string.too_small_error).setPositiveButton( R.string.alert_ok_button, null) .setCancelable(false).show(); return; } long fileSize = outFile.length(); String mimeType = "audio/mpeg"; String artist = "" + getResources().getText(R.string.artist_name); ContentValues values = new ContentValues(); values.put(MediaStore.MediaColumns.DATA, outPath); values.put(MediaStore.MediaColumns.TITLE, title.toString()); values.put(MediaStore.MediaColumns.SIZE, fileSize); values.put(MediaStore.MediaColumns.MIME_TYPE, mimeType); values.put(MediaStore.Audio.Media.ARTIST, artist); values.put(MediaStore.Audio.Media.DURATION, duration); values.put(MediaStore.Audio.Media.IS_RINGTONE, mNewFileKind == FileSaveDialog.FILE_KIND_RINGTONE); values.put(MediaStore.Audio.Media.IS_NOTIFICATION, mNewFileKind == FileSaveDialog.FILE_KIND_NOTIFICATION); values.put(MediaStore.Audio.Media.IS_ALARM, mNewFileKind == FileSaveDialog.FILE_KIND_ALARM); values.put(MediaStore.Audio.Media.IS_MUSIC, mNewFileKind == FileSaveDialog.FILE_KIND_MUSIC); // Insert it into the database Uri uri = MediaStore.Audio.Media.getContentUriForPath(outPath); final Uri newUri = getContentResolver().insert(uri, values); setResult(RESULT_OK, new Intent().setData(newUri)); new AlertDialog.Builder(this).setTitle(R.string.alert_title_success) .setMessage(R.string.alert_okcut_button).setPositiveButton( R.string.alert_ok_trylisten, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // TODO Auto-generated method stub try { lyricView.setBlLrc(false); lyricView.invalidate(); /* 重置MediaPlayer */ mediaPlayer.reset(); /* 设置要播放的文件的路径 */ mediaPlayer.setDataSource(outPath); /* 准备播放 */ mediaPlayer.prepare(); /* 开始播放 */ mediaPlayer.start(); seekBar.setProgress(0); seekBar.setMax(mediaPlayer.getDuration()); System.out.println("mediaPlayer.getDuration()"+mediaPlayer.getDuration()); } catch (Exception e) { // TODO: handle exception } } }).setNegativeButton(R.string.alert_no_button, null) .setCancelable(false).show(); } public int secondsToFrames(double seconds) { return (int) (1.0 * seconds * mSoundFile.getSampleRate() / mSoundFile.getSamplesPerFrame() + 0.5); } private String makeRingtoneFilename(CharSequence title, String extension) { String parentdir = "/sdcard/media/audio/music"; File parentDirFile = new File(parentdir); parentDirFile.mkdirs(); // If we can't write to that special path, try just writing // directly to the sdcard if (!parentDirFile.isDirectory()) { parentdir = "/sdcard"; } // Turn the title into a filename String filename = ""; for (int i = 0; i < title.length(); i++) { if (Character.isLetterOrDigit(title.charAt(i))) { filename += title.charAt(i); } } // Try to make the filename unique String path = null; for (int i = 0; i < 100; i++) { String testPath; if (i > 0) testPath = parentdir + "/" + filename + i + extension; else testPath = parentdir + "/" + filename + extension; try { RandomAccessFile f = new RandomAccessFile(new File(testPath), "r"); } catch (Exception e) { // Good, the file didn't exist path = testPath; break; } } return path; } private String getExtensionFromFilename(String filename) { return filename.substring(filename.lastIndexOf('.'), filename.length()); } class runable implements Runnable { @Override public void run() { // TODO Auto-generated method stub while (true) { try { Thread.sleep(100); if (mediaPlayer.isPlaying()) { lyricView.setOffsetY(lyricView.getOffsetY() - lyricView.SpeedLrc()); lyricView.SelectIndex(mediaPlayer.getCurrentPosition()); seekBar.setProgress(mediaPlayer.getCurrentPosition()); mHandler.post(mUpdateResults); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } Handler mHandler = new Handler(); Runnable mUpdateResults = new Runnable() { public void run() { lyricView.invalidate(); // 更新视图 } }; private void playMusic(int path) { // TODO Auto-generated method stub try { mediaPlayer = new MediaPlayer(); // 重置多媒体 mediaPlayer.reset(); mediaPlayer = MediaPlayer.create(RingdemoActivity.this, path); // 准备播放 if (mediaPlayer != null) { mediaPlayer.stop(); } mediaPlayer.prepare(); } catch (Exception e) { // TODO: handle exception } } private File saveToneToSD(InputStream inputStream, String path) { File file = null; FileUtils fileUtils = new FileUtils(); file = fileUtils.write2SDFromInput(path, "smile.mp3", inputStream); System.out.println("file-->" + file); return file; } private void LoadToneFromSD() { mExtension = getExtensionFromFilename(mFile.getAbsolutePath()); System.out.println("mExtension-->" + mExtension); final CheapSoundFile.ProgressListener listener = new CheapSoundFile.ProgressListener() { public boolean reportProgress(double fractionComplete) { long now = System.currentTimeMillis(); return true; } }; // mSoundFile.create(fileName, progressListener); System.out.println("mFile-->" + mFile.getAbsolutePath()); // mExtension = getExtensionFromFilename(mFile); try { mSoundFile = CheapSoundFile.create(mFile.getAbsolutePath(), listener); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }}
LyricView
package com.hwtt.ringdemo;import java.io.BufferedReader;import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.util.Iterator;import java.util.TreeMap;import java.util.regex.Matcher;import java.util.regex.Pattern;import android.R.integer;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.view.View;public class LyricView extends View{ private static Context context; private static TreeMaplrc_map; private float mX; //屏幕X轴的中点,此值固定,保持歌词在X中间显示 private float offsetY; //歌词在Y轴上的偏移量,此值会根据歌词的滚动变小 private static boolean blLrc=false; private float touchY; //当触摸歌词View时,保存为当前触点的Y轴坐标 private float touchX; private boolean blScrollView=false; private int lrcIndex=0; //保存歌词TreeMap的下标 private int SIZEWORD=0;//显示歌词文字的大小值 private int INTERVAL=45;//歌词每行的间隔 Paint paint=new Paint();//画笔,用于画不是高亮的歌词 Paint paintHL=new Paint(); //画笔,用于画高亮的歌词,即当前唱到这句歌词 public LyricView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); this.context = context; init(); // TODO Auto-generated constructor stub } public LyricView(Context context, AttributeSet attrs) { super(context, attrs); this.context = context; init(); // TODO Auto-generated constructor stub } public LyricView(Context context) { super(context); this.context = context; init(); // TODO Auto-generated constructor stub } public void init(){ lrc_map = new TreeMap (); offsetY=320; paint=new Paint(); paint.setTextAlign(Paint.Align.CENTER); paint.setColor(Color.YELLOW); paint.setAntiAlias(true); paint.setDither(true); paint.setAlpha(180); paintHL=new Paint(); paintHL.setTextAlign(Paint.Align.CENTER); paintHL.setColor(Color.RED); paintHL.setAntiAlias(true); paintHL.setAlpha(255); } @Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub if(blLrc){ paintHL.setTextSize(SIZEWORD); paint.setTextSize(SIZEWORD); LyricObject temp=lrc_map.get(lrcIndex); canvas.drawText(temp.lrc, mX, offsetY+(SIZEWORD+INTERVAL)*lrcIndex, paintHL); // 画当前歌词之前的歌词 for(int i=lrcIndex-1;i>=0;i--){ temp=lrc_map.get(i); if(offsetY+(SIZEWORD+INTERVAL)*i<0){ break; } canvas.drawText(temp.lrc, mX, offsetY+(SIZEWORD+INTERVAL)*i, paint); } // 画当前歌词之后的歌词 for(int i=lrcIndex+1;i 600){ break; } canvas.drawText(temp.lrc, mX, offsetY+(SIZEWORD+INTERVAL)*i, paint); } } else{ paint.setTextSize(25); canvas.drawText("找不到歌词", mX, 310, paint); } super.onDraw(canvas); } @Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub System.out.println("bllll==="+blScrollView); float tt = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: touchX = event.getX(); break; case MotionEvent.ACTION_MOVE: touchY = tt - touchY; offsetY = offsetY + touchY; break; case MotionEvent.ACTION_UP: blScrollView=false; break; default: break; } touchY=tt; return true; } /** * 根据歌词里面最长的那句来确定歌词字体的大小 */ public void SetTextSize(){ if(!blLrc){ return; } int max=lrc_map.get(0).lrc.length(); for(int i=1;i 220){ speed=((offsetY+(SIZEWORD+INTERVAL)*lrcIndex-220)/20); } else if(offsetY+(SIZEWORD+INTERVAL)*lrcIndex < 120){ Log.i("speed", "speed is too fast!!!"); speed = 0; } // if(speed<0.2){ // speed=0.2f; // } return speed; } /** * 按当前的歌曲的播放时间,从歌词里面获得那一句 * @param time 当前歌曲的播放时间 * @return 返回当前歌词的索引值 */ public int SelectIndex(int time){ if(!blLrc){ return 0; } int index=0; for(int i=0;i lrc_read =new TreeMap (); String data = ""; try { if(!blLrc){ return; } blLrc=true; InputStream myInput = context.getResources().openRawResource(R.raw.smilelrc); InputStreamReader reader = new InputStreamReader(myInput,"GB2312"); BufferedReader br = new BufferedReader(reader); int i = 0; Pattern pattern = Pattern.compile("\\d{2}"); while ((data = br.readLine()) != null) { // System.out.println("++++++++++++>>"+data); data = data.replace("[","");//将前面的替换成后面的 data = data.replace("]","@"); String splitdata[] =data.split("@");//分隔 if(data.endsWith("@")){ for(int k=0;k iterator = lrc_read.keySet().iterator(); LyricObject oldval = null; int i =0; while(iterator.hasNext()) { Object ob =iterator.next(); LyricObject val = (LyricObject)lrc_read.get(ob); if (oldval==null) oldval = val; else { LyricObject item1= new LyricObject(); item1 = oldval; item1.timeline = val.begintime-oldval.begintime; lrc_map.put(new Integer(i), item1); i++; oldval = val; } if (!iterator.hasNext()) { lrc_map.put(new Integer(i), val); } } } /** * @return the blLrc */ public static boolean isBlLrc() { return blLrc; } /** * @return the offsetY */ public float getOffsetY() { return offsetY; } /** * @param offsetY the offsetY to set */ public void setOffsetY(float offsetY) { this.offsetY = offsetY; } /** * @return 返回歌词文字的大小 */ public int getSIZEWORD() { return SIZEWORD; } /** * 设置歌词文字的大小 * @param sIZEWORD the sIZEWORD to set */ public void setSIZEWORD(int sIZEWORD) { SIZEWORD = sIZEWORD; } }
LyricObject
package com.hwtt.ringdemo;public class LyricObject { public int begintime; // 开始时间 public int endtime; // 结束时间 public int timeline; // 单句歌词用时 public String lrc; // 单句歌词 }
FileUtils
package com.hwtt.utils;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import android.os.Environment;public class FileUtils { // sd地址 private String SDPATH; // 文件大小 private int FILESIZE = 4 * 1024; public String getSDPATH() { return SDPATH; } public void setSDPATH(String sDPATH) { SDPATH = sDPATH; } public FileUtils() { // 得到当前外部存储设备的目录( /SDCARD ) SDPATH = Environment.getExternalStorageDirectory() + "/"; } /** * 在SD卡上创建文件 * * @param fileName * 创建的文件名称 * @return * @throws IOException */ public File createFile(String fileName) throws IOException { File file = new File(SDPATH+ fileName); file.createNewFile(); return file; } /** * 在sd卡上创建目录 * * @param dirName * @return */ public File createSDDir(String dirName) { File file = new File(SDPATH + dirName); return file; } /** * 判断目录是否存在 */ public boolean isFileExist(String fileName) { File file = new File(SDPATH + fileName); return file.exists(); } public File write2SDFromInput(String path, String fileName, InputStream input) { File file = null; OutputStream output = null; //createSDDir(path); try { file = createFile(fileName); output = new FileOutputStream(file); byte[] buffer = new byte[FILESIZE]; while ((input.read(buffer)) != -1) { output.write(buffer); } output.flush(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { try { output.close(); } catch (IOException e) { e.printStackTrace(); } } return file; }}
FileSaveDialog
/* * 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.hwtt.utils;import android.app.Dialog;import android.content.Context;import android.content.res.Resources;import android.os.Message;import android.util.Log;import android.view.View;import android.widget.AdapterView;import android.widget.AdapterView.OnItemSelectedListener;import android.widget.ArrayAdapter;import android.widget.Button;import android.widget.EditText;import android.widget.Spinner;import java.util.ArrayList;import java.util.HashMap;import com.hwtt.ringdemo.R;public class FileSaveDialog extends Dialog { // File kinds - these should correspond to the order in which // they're presented in the spinner control public static final int FILE_KIND_MUSIC = 0; public static final int FILE_KIND_ALARM = 1; public static final int FILE_KIND_NOTIFICATION = 2; public static final int FILE_KIND_RINGTONE = 3; private Spinner mTypeSpinner; private EditText mFilename; private Message mResponse; private String mOriginalName; private ArrayListmTypeArray; private int mPreviousSelection; /** * Return a human-readable name for a kind (music, alarm, ringtone, ...). * These won't be displayed on-screen (just in logs) so they shouldn't * be translated. */ public static String KindToName(int kind) { switch(kind) { default: return "Unknown"; case FILE_KIND_MUSIC: return "Music"; case FILE_KIND_ALARM: return "Alarm"; case FILE_KIND_NOTIFICATION: return "Notification"; case FILE_KIND_RINGTONE: return "Ringtone"; } } public FileSaveDialog(Context context, Resources resources, String originalName, Message response) { super(context); // Inflate our UI from its XML layout description. setContentView(R.layout.file_save); setTitle(resources.getString(R.string.file_save_title)); mTypeArray = new ArrayList (); mTypeArray.add(resources.getString(R.string.type_music)); mTypeArray.add(resources.getString(R.string.type_alarm)); mTypeArray.add(resources.getString(R.string.type_notification)); mTypeArray.add(resources.getString(R.string.type_ringtone)); mFilename = (EditText)findViewById(R.id.filename); mOriginalName = originalName; ArrayAdapter adapter = new ArrayAdapter ( context, android.R.layout.simple_spinner_item, mTypeArray); adapter.setDropDownViewResource( android.R.layout.simple_spinner_dropdown_item); mTypeSpinner = (Spinner) findViewById(R.id.ringtone_type); mTypeSpinner.setAdapter(adapter); mTypeSpinner.setSelection(FILE_KIND_RINGTONE); mPreviousSelection = FILE_KIND_RINGTONE; setFilenameEditBoxFromName(false); mTypeSpinner.setOnItemSelectedListener(new OnItemSelectedListener() { public void onItemSelected(AdapterView parent, View v, int position, long id) { setFilenameEditBoxFromName(true); } public void onNothingSelected(AdapterView parent) { } }); Button save = (Button)findViewById(R.id.save); save.setOnClickListener(saveListener); Button cancel = (Button)findViewById(R.id.cancel); cancel.setOnClickListener(cancelListener); mResponse = response; } private void setFilenameEditBoxFromName(boolean onlyIfNotEdited) { if (onlyIfNotEdited) { CharSequence currentText = mFilename.getText(); String expectedText = mOriginalName + " " + mTypeArray.get(mPreviousSelection); if (!expectedText.contentEquals(currentText)) { return; } } int newSelection = mTypeSpinner.getSelectedItemPosition(); String newSuffix = mTypeArray.get(newSelection); mFilename.setText(mOriginalName + " " + newSuffix); mPreviousSelection = mTypeSpinner.getSelectedItemPosition(); } private View.OnClickListener saveListener = new View.OnClickListener() { public void onClick(View view) { mResponse.obj = mFilename.getText(); mResponse.arg1 = mTypeSpinner.getSelectedItemPosition(); mResponse.sendToTarget(); dismiss(); } }; private View.OnClickListener cancelListener = new View.OnClickListener() { public void onClick(View view) { dismiss(); } };}
soundfile->CheapAAC
/* * Copyright (C) 2009 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.hwtt.soundfile;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.InputStream;import java.util.HashMap;/** * CheapAAC is a CheapSoundFile implementation for AAC (Advanced Audio * Codec) encoded sound files. It supports files with an MP4 header, * including unencrypted files encoded by Apple iTunes, and also * files with a more basic ADTS header. */public class CheapAAC extends CheapSoundFile { public static Factory getFactory() { return new Factory() { public CheapSoundFile create() { return new CheapAAC(); } public String[] getSupportedExtensions() { return new String[] { "aac", "m4a" }; } }; } class Atom { public int start; public int len; // including header public byte[] data; }; public static final int kDINF = 0x64696e66; public static final int kFTYP = 0x66747970; public static final int kHDLR = 0x68646c72; public static final int kMDAT = 0x6d646174; public static final int kMDHD = 0x6d646864; public static final int kMDIA = 0x6d646961; public static final int kMINF = 0x6d696e66; public static final int kMOOV = 0x6d6f6f76; public static final int kMP4A = 0x6d703461; public static final int kMVHD = 0x6d766864; public static final int kSMHD = 0x736d6864; public static final int kSTBL = 0x7374626c; public static final int kSTCO = 0x7374636f; public static final int kSTSC = 0x73747363; public static final int kSTSD = 0x73747364; public static final int kSTSZ = 0x7374737a; public static final int kSTTS = 0x73747473; public static final int kTKHD = 0x746b6864; public static final int kTRAK = 0x7472616b; public static final int[] kRequiredAtoms = { kDINF, kHDLR, kMDHD, kMDIA, kMINF, kMOOV, kMVHD, kSMHD, kSTBL, kSTSD, kSTSZ, kSTTS, kTKHD, kTRAK, }; public static final int[] kSaveDataAtoms = { kDINF, kHDLR, kMDHD, kMVHD, kSMHD, kTKHD, kSTSD, }; // Member variables containing frame info private int mNumFrames; private int[] mFrameOffsets; private int[] mFrameLens; private int[] mFrameGains; private int mFileSize; private HashMapmAtomMap; // Member variables containing sound file info private int mBitrate; private int mSampleRate; private int mChannels; private int mSamplesPerFrame; // Member variables used only while initially parsing the file private int mOffset; private int mMinGain; private int mMaxGain; private int mMdatOffset; private int mMdatLength; public CheapAAC() { } public int getNumFrames() { return mNumFrames; } public int getSamplesPerFrame() { return mSamplesPerFrame; } public int[] getFrameOffsets() { return mFrameOffsets; } public int[] getFrameLens() { return mFrameLens; } public int[] getFrameGains() { return mFrameGains; } public int getFileSizeBytes() { return mFileSize; } public int getAvgBitrateKbps() { return mFileSize / (mNumFrames * mSamplesPerFrame); } public int getSampleRate() { return mSampleRate; } public int getChannels() { return mChannels; } public String getFiletype() { return "AAC"; } public String atomToString(int atomType) { String str = ""; str += (char)((atomType >> 24) & 0xff); str += (char)((atomType >> 16) & 0xff); str += (char)((atomType >> 8) & 0xff); str += (char)(atomType & 0xff); return str; } public void ReadFile(File inputFile) throws java.io.FileNotFoundException, java.io.IOException { super.ReadFile(inputFile); mChannels = 0; mSampleRate = 0; mBitrate = 0; mSamplesPerFrame = 0; mNumFrames = 0; mMinGain = 255; mMaxGain = 0; mOffset = 0; mMdatOffset = -1; mMdatLength = -1; mAtomMap = new HashMap (); // No need to handle filesizes larger than can fit in a 32-bit int mFileSize = (int)mInputFile.length(); /*System.out.println("File size = " + mFileSize);*/ if (mFileSize < 128) { throw new java.io.IOException("File too small to parse"); } // Read the first 8 bytes FileInputStream stream = new FileInputStream(mInputFile); byte[] header = new byte[8]; stream.read(header, 0, 8); if (header[0] == 0 && header[4] == 'f' && header[5] == 't' && header[6] == 'y' && header[7] == 'p') { // Create a new stream, reset to the beginning of the file stream = new FileInputStream(mInputFile); parseMp4(stream, mFileSize); } else { throw new java.io.IOException("Unknown file format"); } if (mMdatOffset > 0 && mMdatLength > 0) { stream = new FileInputStream(mInputFile); stream.skip(mMdatOffset); mOffset = mMdatOffset; parseMdat(stream, mMdatLength); } else { throw new java.io.IOException("Didn't find mdat"); } /* for (int i = 0; i < mNumFrames; i++) { System.out.println("Gain " + i + ": " + mFrameGains[i]); }*/ /* System.out.println("Atoms found:"); for (int atomType : mAtomMap.keySet()) { System.out.println(" " + atomToString(atomType)); }*/ boolean bad = false; for (int requiredAtomType : kRequiredAtoms) { if (!mAtomMap.containsKey(requiredAtomType)) { System.out.println("Missing atom: " + atomToString(requiredAtomType)); bad = true; } } if (bad) { throw new java.io.IOException("Could not parse MP4 file"); } } private void parseMp4(InputStream stream, int maxLen) throws java.io.IOException { /*System.out.println("parseMp4 maxLen = " + maxLen);*/ while (maxLen > 8) { int initialOffset = mOffset; byte[] atomHeader = new byte[8]; stream.read(atomHeader, 0, 8); int atomLen = ((0xff & atomHeader[0]) << 24) | ((0xff & atomHeader[1]) << 16) | ((0xff & atomHeader[2]) << 8) | ((0xff & atomHeader[3])); /*System.out.println("atomType = " + (char)atomHeader[4] + (char)atomHeader[5] + (char)atomHeader[6] + (char)atomHeader[7] + " " + "offset = " + mOffset + " " + "atomLen = " + atomLen);*/ if (atomLen > maxLen) atomLen = maxLen; int atomType = ((0xff & atomHeader[4]) << 24) | ((0xff & atomHeader[5]) << 16) | ((0xff & atomHeader[6]) << 8) | ((0xff & atomHeader[7])); Atom atom = new Atom(); atom.start = mOffset; atom.len = atomLen; mAtomMap.put(atomType, atom); mOffset += 8; if (atomType == kMOOV || atomType == kTRAK || atomType == kMDIA || atomType == kMINF || atomType == kSTBL) { parseMp4(stream, atomLen); } else if (atomType == kSTSZ) { parseStsz(stream, atomLen - 8); } else if (atomType == kSTTS) { parseStts(stream, atomLen - 8); } else if (atomType == kMDAT) { mMdatOffset = mOffset; mMdatLength = atomLen - 8; } else { for (int savedAtomType : kSaveDataAtoms) { if (savedAtomType == atomType) { byte[] data = new byte[atomLen - 8]; stream.read(data, 0, atomLen - 8); mOffset += atomLen - 8; mAtomMap.get(atomType).data = data; } } } if (atomType == kSTSD) { parseMp4aFromStsd(); } maxLen -= atomLen; int skipLen = atomLen - (mOffset - initialOffset); /*System.out.println("* atomLen: " + atomLen);*/ /*System.out.println("* mOffset: " + mOffset);*/ /*System.out.println("* initialOffset: " + initialOffset);*/ /*System.out.println("* diff: " + (mOffset - initialOffset));*/ /*System.out.println("* skipLen: " + skipLen);*/ if (skipLen < 0) { throw new java.io.IOException( "Went over by " + (-skipLen) + " bytes"); } stream.skip(skipLen); mOffset += skipLen; } } void parseStts(InputStream stream, int maxLen) throws java.io.IOException { byte[] sttsData = new byte[16]; stream.read(sttsData, 0, 16); mOffset += 16; mSamplesPerFrame = ((0xff & sttsData[12]) << 24) | ((0xff & sttsData[13]) << 16) | ((0xff & sttsData[14]) << 8) | ((0xff & sttsData[15])); /*System.out.println("STTS samples per frame: " + mSamplesPerFrame);*/ } void parseStsz(InputStream stream, int maxLen) throws java.io.IOException { byte[] stszHeader = new byte[12]; stream.read(stszHeader, 0, 12); mOffset += 12; mNumFrames = ((0xff & stszHeader[8]) << 24) | ((0xff & stszHeader[9]) << 16) | ((0xff & stszHeader[10]) << 8) | ((0xff & stszHeader[11])); /*System.out.println("mNumFrames = " + mNumFrames);*/ mFrameOffsets = new int[mNumFrames]; mFrameLens = new int[mNumFrames]; mFrameGains = new int[mNumFrames]; byte[] frameLenBytes = new byte[4 * mNumFrames]; stream.read(frameLenBytes, 0, 4 * mNumFrames); mOffset += 4 * mNumFrames; for (int i = 0; i < mNumFrames; i++) { mFrameLens[i] = ((0xff & frameLenBytes[4 * i + 0]) << 24) | ((0xff & frameLenBytes[4 * i + 1]) << 16) | ((0xff & frameLenBytes[4 * i + 2]) << 8) | ((0xff & frameLenBytes[4 * i + 3])); /*System.out.println("FrameLen[" + i + "] = " + mFrameLens[i]);*/ } } void parseMp4aFromStsd() { byte[] stsdData = mAtomMap.get(kSTSD).data; mChannels = ((0xff & stsdData[32]) << 8) | ((0xff & stsdData[33])); mSampleRate = ((0xff & stsdData[40]) << 8) | ((0xff & stsdData[41])); /*System.out.println("%% channels = " + mChannels + ", " + "sampleRate = " + mSampleRate);*/ } void parseMdat(InputStream stream, int maxLen) throws java.io.IOException { /*System.out.println("***MDAT***");*/ int initialOffset = mOffset; for (int i = 0; i < mNumFrames; i++) { mFrameOffsets[i] = mOffset; /*System.out.println("&&& start: " + (mOffset - initialOffset));*/ /*System.out.println("&&& start + len: " + (mOffset - initialOffset + mFrameLens[i]));*/ /*System.out.println("&&& maxLen: " + maxLen);*/ if (mOffset - initialOffset + mFrameLens[i] > maxLen - 8) { mFrameGains[i] = 0; } else { readFrameAndComputeGain(stream, i); } if (mFrameGains[i] < mMinGain) mMinGain = mFrameGains[i]; if (mFrameGains[i] > mMaxGain) mMaxGain = mFrameGains[i]; if (mProgressListener != null) { boolean keepGoing = mProgressListener.reportProgress( mOffset * 1.0 / mFileSize); if (!keepGoing) { break; } } } } void readFrameAndComputeGain(InputStream stream, int frameIndex) throws java.io.IOException { if (mFrameLens[frameIndex] < 4) { mFrameGains[frameIndex] = 0; stream.skip(mFrameLens[frameIndex]); return; } int initialOffset = mOffset; byte[] data = new byte[4]; stream.read(data, 0, 4); mOffset += 4; /*System.out.println( "Block " + frameIndex + ": " + data[0] + " " + data[1] + " " + data[2] + " " + data[3]);*/ int idSynEle = (0xe0 & data[0]) >> 5; /*System.out.println("idSynEle = " + idSynEle);*/ switch(idSynEle) { case 0: // ID_SCE: mono int monoGain = ((0x01 & data[0]) << 7) | ((0xfe & data[1]) >> 1); /*System.out.println("monoGain = " + monoGain);*/ mFrameGains[frameIndex] = monoGain; break; case 1: // ID_CPE: stereo int windowSequence = (0x60 & data[1]) >> 5; /*System.out.println("windowSequence = " + windowSequence);*/ int windowShape = (0x10 & data[1]) >> 4; /*System.out.println("windowShape = " + windowShape);*/ int maxSfb; int scaleFactorGrouping; int maskPresent; int startBit; if (windowSequence == 2) { maxSfb = 0x0f & data[1]; scaleFactorGrouping = (0xfe & data[2]) >> 1; maskPresent = ((0x01 & data[2]) << 1) | ((0x80 & data[3]) >> 7); startBit = 25; } else { maxSfb = ((0x0f & data[1]) << 2) | ((0xc0 & data[2]) >> 6); scaleFactorGrouping = -1; maskPresent = (0x18 & data[2]) >> 3; startBit = 21; } /*System.out.println("maxSfb = " + maxSfb);*/ /*System.out.println("scaleFactorGrouping = " + scaleFactorGrouping);*/ /*System.out.println("maskPresent = " + maskPresent);*/ /*System.out.println("startBit = " + startBit);*/ if (maskPresent == 1) { int sfgZeroBitCount = 0; for (int b = 0; b < 7; b++) { if ((scaleFactorGrouping & (1 << b)) == 0) { /*System.out.println(" 1 point for bit " + b + ": " + (1 << b) + ", " + (scaleFactorGrouping & (1 << b)));*/ sfgZeroBitCount++; } } /*System.out.println("sfgZeroBitCount = " + sfgZeroBitCount);*/ int numWindowGroups = 1 + sfgZeroBitCount; /*System.out.println("numWindowGroups = " + numWindowGroups);*/ int skip = maxSfb * numWindowGroups; /*System.out.println("skip = " + skip);*/ startBit += skip; /*System.out.println("new startBit = " + startBit);*/ } // We may need to fill our buffer with more than the 4 // bytes we've already read, here. int bytesNeeded = 1 + ((startBit + 7) / 8); byte[] oldData = data; data = new byte[bytesNeeded]; data[0] = oldData[0]; data[1] = oldData[1]; data[2] = oldData[2]; data[3] = oldData[3]; stream.read(data, 4, bytesNeeded - 4); mOffset += (bytesNeeded - 4); /*System.out.println("bytesNeeded: " + bytesNeeded);*/ int firstChannelGain = 0; for (int b = 0; b < 8; b++) { int b0 = (b + startBit) / 8; int b1 = 7 - ((b + startBit) % 8); int add = (((1 << b1) & data[b0]) >> b1) << (7 - b); /*System.out.println("Bit " + (b + startBit) + " " + "b0 " + b0 + " " + "b1 " + b1 + " " + "add " + add);*/ firstChannelGain += add; } /*System.out.println("firstChannelGain = " + firstChannelGain);*/ mFrameGains[frameIndex] = firstChannelGain; break; default: if (frameIndex > 0) { mFrameGains[frameIndex] = mFrameGains[frameIndex - 1]; } else { mFrameGains[frameIndex] = 0; } /*System.out.println("Unhandled idSynEle");*/ break; } int skip = mFrameLens[frameIndex] - (mOffset - initialOffset); /*System.out.println("frameLen = " + mFrameLens[frameIndex]);*/ /*System.out.println("Skip = " + skip);*/ stream.skip(skip); mOffset += skip; } public void StartAtom(FileOutputStream out, int atomType) throws java.io.IOException { byte[] atomHeader = new byte[8]; int atomLen = mAtomMap.get(atomType).len; atomHeader[0] = (byte)((atomLen >> 24) & 0xff); atomHeader[1] = (byte)((atomLen >> 16) & 0xff); atomHeader[2] = (byte)((atomLen >> 8) & 0xff); atomHeader[3] = (byte)(atomLen & 0xff); atomHeader[4] = (byte)((atomType >> 24) & 0xff); atomHeader[5] = (byte)((atomType >> 16) & 0xff); atomHeader[6] = (byte)((atomType >> 8) & 0xff); atomHeader[7] = (byte)(atomType & 0xff); out.write(atomHeader, 0, 8); } public void WriteAtom(FileOutputStream out, int atomType) throws java.io.IOException { Atom atom = mAtomMap.get(atomType); StartAtom(out, atomType); out.write(atom.data, 0, atom.len - 8); } public void SetAtomData(int atomType, byte[] data) { Atom atom = mAtomMap.get(atomType); if (atom == null) { atom = new Atom(); mAtomMap.put(atomType, atom); } atom.len = data.length + 8; atom.data = data; } public void WriteFile(File outputFile, int startFrame, int numFrames) throws java.io.IOException { outputFile.createNewFile(); FileInputStream in = new FileInputStream(mInputFile); FileOutputStream out = new FileOutputStream(outputFile); SetAtomData(kFTYP, new byte[] { 'M', '4', 'A', ' ', 0, 0, 0, 0, 'M', '4', 'A', ' ', 'm', 'p', '4', '2', 'i', 's', 'o', 'm', 0, 0, 0, 0 }); SetAtomData(kSTTS, new byte[] { 0, 0, 0, 0, // version / flags 0, 0, 0, 1, // entry count (byte) ((numFrames >> 24) & 0xff), (byte) ((numFrames >> 16) & 0xff), (byte) ((numFrames >> 8) & 0xff), (byte) (numFrames & 0xff), (byte) ((mSamplesPerFrame >> 24) & 0xff), (byte) ((mSamplesPerFrame >> 16) & 0xff), (byte) ((mSamplesPerFrame >> 8) & 0xff), (byte) (mSamplesPerFrame & 0xff) }); SetAtomData(kSTSC, new byte[] { 0, 0, 0, 0, // version / flags 0, 0, 0, 1, // entry count 0, 0, 0, 1, // first chunk (byte) ((numFrames >> 24) & 0xff), (byte) ((numFrames >> 16) & 0xff), (byte) ((numFrames >> 8) & 0xff), (byte) (numFrames & 0xff), 0, 0, 0, 1 // Smaple desc index }); byte[] stszData = new byte[12 + 4 * numFrames]; stszData[8] = (byte)((numFrames >> 24) & 0xff); stszData[9] = (byte)((numFrames >> 16) & 0xff); stszData[10] = (byte)((numFrames >> 8) & 0xff); stszData[11] = (byte)(numFrames & 0xff); for (int i = 0; i < numFrames; i++) { stszData[12 + 4 * i] = (byte)((mFrameLens[startFrame + i] >> 24) & 0xff); stszData[13 + 4 * i] = (byte)((mFrameLens[startFrame + i] >> 16) & 0xff); stszData[14 + 4 * i] = (byte)((mFrameLens[startFrame + i] >> 8) & 0xff); stszData[15 + 4 * i] = (byte)(mFrameLens[startFrame + i] & 0xff); } SetAtomData(kSTSZ, stszData); int mdatOffset = 144 + 4 * numFrames + mAtomMap.get(kSTSD).len + mAtomMap.get(kSTSC).len + mAtomMap.get(kMVHD).len + mAtomMap.get(kTKHD).len + mAtomMap.get(kMDHD).len + mAtomMap.get(kHDLR).len + mAtomMap.get(kSMHD).len + mAtomMap.get(kDINF).len; /*System.out.println("Mdat offset: " + mdatOffset);*/ SetAtomData(kSTCO, new byte[] { 0, 0, 0, 0, // version / flags 0, 0, 0, 1, // entry count (byte) ((mdatOffset >> 24) & 0xff), (byte) ((mdatOffset >> 16) & 0xff), (byte) ((mdatOffset >> 8) & 0xff), (byte) (mdatOffset & 0xff), }); mAtomMap.get(kSTBL).len = 8 + mAtomMap.get(kSTSD).len + mAtomMap.get(kSTTS).len + mAtomMap.get(kSTSC).len + mAtomMap.get(kSTSZ).len + mAtomMap.get(kSTCO).len; mAtomMap.get(kMINF).len = 8 + mAtomMap.get(kDINF).len + mAtomMap.get(kSMHD).len + mAtomMap.get(kSTBL).len; mAtomMap.get(kMDIA).len = 8 + mAtomMap.get(kMDHD).len + mAtomMap.get(kHDLR).len + mAtomMap.get(kMINF).len; mAtomMap.get(kTRAK).len = 8 + mAtomMap.get(kTKHD).len + mAtomMap.get(kMDIA).len; mAtomMap.get(kMOOV).len = 8 + mAtomMap.get(kMVHD).len + mAtomMap.get(kTRAK).len; int mdatLen = 8; for (int i = 0; i < numFrames; i++) { mdatLen += mFrameLens[startFrame + i]; } mAtomMap.get(kMDAT).len = mdatLen; WriteAtom(out, kFTYP); StartAtom(out, kMOOV); { WriteAtom(out, kMVHD); StartAtom(out, kTRAK); { WriteAtom(out, kTKHD); StartAtom(out, kMDIA); { WriteAtom(out, kMDHD); WriteAtom(out, kHDLR); StartAtom(out, kMINF); { WriteAtom(out, kDINF); WriteAtom(out, kSMHD); StartAtom(out, kSTBL); { WriteAtom(out, kSTSD); WriteAtom(out, kSTTS); WriteAtom(out, kSTSC); WriteAtom(out, kSTSZ); WriteAtom(out, kSTCO); } } } } } StartAtom(out, kMDAT); int maxFrameLen = 0; for (int i = 0; i < numFrames; i++) { if (mFrameLens[startFrame + i] > maxFrameLen) maxFrameLen = mFrameLens[startFrame + i]; } byte[] buffer = new byte[maxFrameLen]; int pos = 0; for (int i = 0; i < numFrames; i++) { int skip = mFrameOffsets[startFrame + i] - pos; int len = mFrameLens[startFrame + i]; if (skip < 0) { continue; } if (skip > 0) { in.skip(skip); pos += skip; } in.read(buffer, 0, len); out.write(buffer, 0, len); pos += len; } in.close(); out.close(); } /** For debugging public static void main(String[] argv) throws Exception { File f = new File(""); CheapAAC c = new CheapAAC(); c.ReadFile(f); c.WriteFile(new File(""), 0, c.getNumFrames()); } **/};
CheapAMR
/* * 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.hwtt.soundfile;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.InputStream;import java.lang.Math;/** * CheapAMR is a CheapSoundFile implementation for AMR (Adaptive Multi-Rate) * encoded sound files, which is one of the native formats supported by * Android's MediaRecorder library. It supports files with a full 3GPP * header, and also files with only a basic AMR header. * * While there are 8 bitrates and several other frame types in AMR, * this implementation currently only supports frametype=1, * MR515, 10.3 kbits / sec, which is the format encoded on Android 1.0 * phones. In the future it may be necessary to support other bitrates. */public class CheapAMR extends CheapSoundFile { public static Factory getFactory() { return new Factory() { public CheapSoundFile create() { return new CheapAMR(); } public String[] getSupportedExtensions() { return new String[] { "3gpp", "3gp", "amr" }; } }; } // Member variables containing frame info private int mNumFrames; private int[] mFrameOffsets; private int[] mFrameLens; private int[] mFrameGains; private int mFileSize; private int mBitRate; // Member variables used only while initially parsing the file private int mOffset; private int mMaxFrames; private int mMinGain; private int mMaxGain; public CheapAMR() { } public int getNumFrames() { return mNumFrames; } public int getSamplesPerFrame() { return 40; } public int[] getFrameOffsets() { return mFrameOffsets; } public int[] getFrameLens() { return mFrameLens; } public int[] getFrameGains() { return mFrameGains; } public int getFileSizeBytes() { return mFileSize; } public int getAvgBitrateKbps() { return mBitRate; } public int getSampleRate() { return 8000; } public int getChannels() { return 1; } public String getFiletype() { return "AMR"; } public void ReadFile(File inputFile) throws java.io.FileNotFoundException, java.io.IOException { super.ReadFile(inputFile); mNumFrames = 0; mMaxFrames = 64; // This will grow as needed mFrameOffsets = new int[mMaxFrames]; mFrameLens = new int[mMaxFrames]; mFrameGains = new int[mMaxFrames]; mMinGain = 1000000000; mMaxGain = 0; mBitRate = 10; mOffset = 0; // No need to handle filesizes larger than can fit in a 32-bit int mFileSize = (int)mInputFile.length(); if (mFileSize < 128) { throw new java.io.IOException("File too small to parse"); } FileInputStream stream = new FileInputStream(mInputFile); byte[] header = new byte[12]; stream.read(header, 0, 6); mOffset += 6; if (header[0] == '#' && header[1] == '!' && header[2] == 'A' && header[3] == 'M' && header[4] == 'R' && header[5] == '\n') { parseAMR(stream, mFileSize - 6); } stream.read(header, 6, 6); mOffset += 6; if (header[4] == 'f' && header[5] == 't' && header[6] == 'y' && header[7] == 'p' && header[8] == '3' && header[9] == 'g' && header[10] == 'p' && header[11] == '4') { int boxLen = ((0xff & header[0]) << 24) | ((0xff & header[1]) << 16) | ((0xff & header[2]) << 8) | ((0xff & header[3])); if (boxLen >= 4 && boxLen <= mFileSize - 8) { stream.skip(boxLen - 12); mOffset += boxLen - 12; } parse3gpp(stream, mFileSize - boxLen); } } private void parse3gpp(InputStream stream, int maxLen) throws java.io.IOException { if (maxLen < 8) return; byte[] boxHeader = new byte[8]; stream.read(boxHeader, 0, 8); mOffset += 8; int boxLen = ((0xff & boxHeader[0]) << 24) | ((0xff & boxHeader[1]) << 16) | ((0xff & boxHeader[2]) << 8) | ((0xff & boxHeader[3])); if (boxLen > maxLen || boxLen <= 0) return; if (boxHeader[4] == 'm' && boxHeader[5] == 'd' && boxHeader[6] == 'a' && boxHeader[7] == 't') { parseAMR(stream, boxLen); return; } stream.skip(boxLen - 8); mOffset += (boxLen - 8); parse3gpp(stream, maxLen - boxLen); } void parseAMR(InputStream stream, int maxLen) throws java.io.IOException { int[] prevEner = new int[4]; for (int i = 0; i < 4; i++) { prevEner[i] = 0; } int[] prevEnerMR122 = new int[4]; for (int i = 0; i < 4; i++) { prevEnerMR122[i] = -2381; } int originalMaxLen = maxLen; int bytesTotal = 0; while (maxLen > 0) { int bytesConsumed = parseAMRFrame(stream, maxLen, prevEner); bytesTotal += bytesConsumed; maxLen -= bytesConsumed; if (mProgressListener != null) { boolean keepGoing = mProgressListener.reportProgress( bytesTotal * 1.0 / originalMaxLen); if (!keepGoing) { break; } } } } int parseAMRFrame(InputStream stream, int maxLen, int[] prevEner) throws java.io.IOException { int frameOffset = mOffset; byte[] frameTypeHeader = new byte[1]; stream.read(frameTypeHeader, 0, 1); mOffset += 1; int frameType = ((0xff & frameTypeHeader[0]) >> 3) % 0x0F; int frameQuality = ((0xff & frameTypeHeader[0]) >> 2) & 0x01; int blockSize = BLOCK_SIZES[frameType]; if (blockSize + 1 > maxLen) { // We can't read the full frame, so consume the remaining // bytes to end processing the AMR stream. return maxLen; } if (blockSize == 0) { return 1; } byte[] v = new byte[blockSize]; stream.read(v, 0, blockSize); mOffset += blockSize; int[] bits = new int[blockSize * 8]; int ii = 0; int value = 0xff & v[ii]; for (int i = 0; i < blockSize * 8; i++) { bits[i] = ((value & 0x80) >> 7); value <<= 1; if ((i & 0x07) == 0x07 && i < blockSize * 8 - 1) { ii += 1; value = 0xff & v[ii]; } } int[] gain; switch (frameType) { case 0: mBitRate = 5; gain = new int[4]; gain[0] = 0x01 * bits[28] + 0x02 * bits[29] + 0x04 * bits[30] + 0x08 * bits[31] + 0x10 * bits[46] + 0x20 * bits[47] + 0x40 * bits[48] + 0x80 * bits[49]; gain[1] = gain[0]; gain[2] = 0x01 * bits[32] + 0x02 * bits[33] + 0x04 * bits[34] + 0x08 * bits[35] + 0x10 * bits[40] + 0x20 * bits[41] + 0x40 * bits[42] + 0x80 * bits[43]; gain[3] = gain[2]; for (int i = 0; i < 4; i++) { int index = gain[i] * 4 + (i & 1) * 2 + 1; int gFac = GAIN_FAC_MR475[index]; double log2 = Math.log(gFac) / Math.log(2); int exp = (int)log2; int frac = (int)((log2 - exp) * 32768); exp -= 12; int tmp = exp * 49320; tmp += ((frac * 24660) >> 15) * 2; int quaEner = ((tmp * 8192) + 0x8000) >> 16; int gcode0 = (385963008 + prevEner[0] * 5571 + prevEner[1] * 4751 + prevEner[2] * 2785 + prevEner[3] * 1556) >> 15; prevEner[3] = prevEner[2]; prevEner[2] = prevEner[1]; prevEner[1] = prevEner[0]; prevEner[0] = quaEner; int frameGainEstimate = (gcode0 * gFac) >> 24; addFrame(frameOffset, blockSize + 1, frameGainEstimate); } break; case 1: mBitRate = 5; gain = new int[4]; gain[0] = 0x01 * bits[24] + 0x02 * bits[25] + 0x04 * bits[26] + 0x08 * bits[36] + 0x10 * bits[45] + 0x20 * bits[55]; gain[1] = 0x01 * bits[27] + 0x02 * bits[28] + 0x04 * bits[29] + 0x08 * bits[37] + 0x10 * bits[46] + 0x20 * bits[56]; gain[2] = 0x01 * bits[30] + 0x02 * bits[31] + 0x04 * bits[32] + 0x08 * bits[38] + 0x10 * bits[47] + 0x20 * bits[57]; gain[3] = 0x01 * bits[33] + 0x02 * bits[34] + 0x04 * bits[35] + 0x08 * bits[39] + 0x10 * bits[48] + 0x20 * bits[58]; for (int i = 0; i < 4; i++) { int gcode0 = (385963008 + prevEner[0] * 5571 + prevEner[1] * 4751 + prevEner[2] * 2785 + prevEner[3] * 1556) >> 15; int quaEner = QUA_ENER_MR515[gain[i]]; int gFac = GAIN_FAC_MR515[gain[i]]; prevEner[3] = prevEner[2]; prevEner[2] = prevEner[1]; prevEner[1] = prevEner[0]; prevEner[0] = quaEner; int frameGainEstimate = (gcode0 * gFac) >> 24; addFrame(frameOffset, blockSize + 1, frameGainEstimate); } break; case 7: mBitRate = 12; int[] adaptiveIndex = new int[4]; int[] adaptiveGain = new int[4]; int[] fixedGain = new int[4]; int[][] pulse = new int[4][]; for (int i = 0; i < 4; i++) { pulse[i] = new int[10]; } getMR122Params(bits, adaptiveIndex, adaptiveGain, fixedGain, pulse); int T0 = 0; for (int subframe = 0; subframe < 4; subframe++) { int[] code = new int[40]; for (int i = 0; i < 40; i++) { code[i] = 0; } int sign; for (int j = 0; j < 5; j++) { if (((pulse[subframe][j] >> 3) & 1) == 0) { sign = 4096; } else { sign = -4096; } int pos1 = j + GRAY[pulse[subframe][j] & 7] * 5; int pos2 = j + GRAY[pulse[subframe][j + 5] & 7] * 5; code[pos1] = sign; if (pos2 < pos1) { sign = -sign; } code[pos2] = code[pos2] + sign; } int index = adaptiveIndex[subframe]; if (subframe == 0 || subframe == 2) { if (index < 463) { T0 = (index + 5) / 6 + 17; } else { T0 = index - 368; } } else { int pitMin = 18; int pitMax = 143; int T0Min = T0 - 5; if (T0Min < pitMin) { T0Min = pitMin; } int T0Max = T0Min + 9; if (T0Max > pitMax) { T0Max = pitMax; T0Min = T0Max - 9; } T0 = T0Min + (index + 5) / 6 - 1; } int pitSharp = (QUA_GAIN_PITCH[adaptiveGain[subframe]] >> 2) << 2; if (pitSharp > 16383) { pitSharp = 32767; } else { pitSharp *= 2; } for (int j = T0; j < 40; j++) { code[j] += (code[j - T0] * pitSharp) >> 15; } int enerCode = 0; for (int j = 0; j < 40; j++) { enerCode += code[j] * code[j]; } if ((0x3fffffff <= enerCode) || (enerCode < 0)) { enerCode = 0x7fffffff; } else { enerCode *= 2; } enerCode = ((enerCode + 0x8000) >> 16) * 52428; double log2 = Math.log(enerCode) / Math.log(2); int exp = (int)log2; int frac = (int)((log2 - exp) * 32768); enerCode = ((exp - 30) << 16) + (frac * 2); int ener = prevEner[0] * 44 + prevEner[1] * 37 + prevEner[2] * 22 + prevEner[3] * 12; ener = 2 * ener + 783741; ener = (ener - enerCode) / 2; int expGCode = ener >> 16; int fracGCode = (ener >> 1) - (expGCode << 15); int gCode0 = (int) (Math.pow(2.0, expGCode + (fracGCode / 32768.0)) + 0.5); if (gCode0 <= 2047) { gCode0 = gCode0 << 4; } else { gCode0 = 32767; } index = fixedGain[subframe]; int gainCode = ((gCode0 * QUA_GAIN_CODE[3 * index]) >> 15) << 1; if ((gainCode & 0xFFFF8000) != 0) { gainCode = 32767; } int frameGainEstimate = gainCode; addFrame(frameOffset, blockSize + 1, frameGainEstimate); int quaEnerMR122 = QUA_GAIN_CODE[3 * index + 1]; prevEner[3] = prevEner[2]; prevEner[2] = prevEner[1]; prevEner[1] = prevEner[0]; prevEner[0] = quaEnerMR122; } break; default: System.out.println("Unsupported frame type: " + frameType); addFrame(frameOffset, blockSize + 1, 1); break; } // Return number of bytes consumed return blockSize + 1; } void addFrame(int offset, int frameSize, int gain) { mFrameOffsets[mNumFrames] = offset; mFrameLens[mNumFrames] = frameSize; mFrameGains[mNumFrames] = gain; if (gain < mMinGain) mMinGain = gain; if (gain > mMaxGain) mMaxGain = gain; mNumFrames++; if (mNumFrames == mMaxFrames) { int newMaxFrames = mMaxFrames * 2; int[] newOffsets = new int[newMaxFrames]; int[] newLens = new int[newMaxFrames]; int[] newGains = new int[newMaxFrames]; for (int i = 0; i < mNumFrames; i++) { newOffsets[i] = mFrameOffsets[i]; newLens[i] = mFrameLens[i]; newGains[i] = mFrameGains[i]; } mFrameOffsets = newOffsets; mFrameLens = newLens; mFrameGains = newGains; mMaxFrames = newMaxFrames; } } public void WriteFile(File outputFile, int startFrame, int numFrames) throws java.io.IOException { outputFile.createNewFile(); FileInputStream in = new FileInputStream(mInputFile); FileOutputStream out = new FileOutputStream(outputFile); byte[] header = new byte[6]; header[0] = '#'; header[1] = '!'; header[2] = 'A'; header[3] = 'M'; header[4] = 'R'; header[5] = '\n'; out.write(header, 0, 6); int maxFrameLen = 0; for (int i = 0; i < numFrames; i++) { if (mFrameLens[startFrame + i] > maxFrameLen) maxFrameLen = mFrameLens[startFrame + i]; } byte[] buffer = new byte[maxFrameLen]; int pos = 0; for (int i = 0; i < numFrames; i++) { int skip = mFrameOffsets[startFrame + i] - pos; int len = mFrameLens[startFrame + i]; if (skip < 0) { continue; } if (skip > 0) { in.skip(skip); pos += skip; } in.read(buffer, 0, len); out.write(buffer, 0, len); pos += len; } in.close(); out.close(); } void getMR122Params(int[] bits, int[] adaptiveIndex, int[] adaptiveGain, int[] fixedGain, int[][] pulse) { adaptiveIndex[0] = 0x01 * bits[45] + 0x02 * bits[43] + 0x04 * bits[41] + 0x08 * bits[39] + 0x10 * bits[37] + 0x20 * bits[35] + 0x40 * bits[33] + 0x80 * bits[31] + 0x100 * bits[29]; adaptiveIndex[1] = 0x01 * bits[242] + 0x02 * bits[79] + 0x04 * bits[77] + 0x08 * bits[75] + 0x10 * bits[73] + 0x20 * bits[71]; adaptiveIndex[2] = 0x01 * bits[46] + 0x02 * bits[44] + 0x04 * bits[42] + 0x08 * bits[40] + 0x10 * bits[38] + 0x20 * bits[36] + 0x40 * bits[34] + 0x80 * bits[32] + 0x100 * bits[30]; adaptiveIndex[3] = 0x01 * bits[243] + 0x02 * bits[80] + 0x04 * bits[78] + 0x08 * bits[76] + 0x10 * bits[74] + 0x20 * bits[72]; adaptiveGain[0] = 0x01 * bits[88] + 0x02 * bits[55] + 0x04 * bits[51] + 0x08 * bits[47]; adaptiveGain[1] = 0x01 * bits[89] + 0x02 * bits[56] + 0x04 * bits[52] + 0x08 * bits[48]; adaptiveGain[2] = 0x01 * bits[90] + 0x02 * bits[57] + 0x04 * bits[53] + 0x08 * bits[49]; adaptiveGain[3] = 0x01 * bits[91] + 0x02 * bits[58] + 0x04 * bits[54] + 0x08 * bits[50]; fixedGain[0] = 0x01 * bits[104] + 0x02 * bits[92] + 0x04 * bits[67] + 0x08 * bits[63] + 0x10 * bits[59]; fixedGain[1] = 0x01 * bits[105] + 0x02 * bits[93] + 0x04 * bits[68] + 0x08 * bits[64] + 0x10 * bits[60]; fixedGain[2] = 0x01 * bits[106] + 0x02 * bits[94] + 0x04 * bits[69] + 0x08 * bits[65] + 0x10 * bits[61]; fixedGain[3] = 0x01 * bits[107] + 0x02 * bits[95] + 0x04 * bits[70] + 0x08 * bits[66] + 0x10 * bits[62]; pulse[0][0] = 0x01 * bits[122] + 0x02 * bits[123] + 0x04 * bits[124] + 0x08 * bits[96]; pulse[0][1] = 0x01 * bits[125] + 0x02 * bits[126] + 0x04 * bits[127] + 0x08 * bits[100]; pulse[0][2] = 0x01 * bits[128] + 0x02 * bits[129] + 0x04 * bits[130] + 0x08 * bits[108]; pulse[0][3] = 0x01 * bits[131] + 0x02 * bits[132] + 0x04 * bits[133] + 0x08 * bits[112]; pulse[0][4] = 0x01 * bits[134] + 0x02 * bits[135] + 0x04 * bits[136] + 0x08 * bits[116]; pulse[0][5] = 0x01 * bits[182] + 0x02 * bits[183] + 0x04 * bits[184]; pulse[0][6] = 0x01 * bits[185] + 0x02 * bits[186] + 0x04 * bits[187]; pulse[0][7] = 0x01 * bits[188] + 0x02 * bits[189] + 0x04 * bits[190]; pulse[0][8] = 0x01 * bits[191] + 0x02 * bits[192] + 0x04 * bits[193]; pulse[0][9] = 0x01 * bits[194] + 0x02 * bits[195] + 0x04 * bits[196]; pulse[1][0] = 0x01 * bits[137] + 0x02 * bits[138] + 0x04 * bits[139] + 0x08 * bits[97]; pulse[1][1] = 0x01 * bits[140] + 0x02 * bits[141] + 0x04 * bits[142] + 0x08 * bits[101]; pulse[1][2] = 0x01 * bits[143] + 0x02 * bits[144] + 0x04 * bits[145] + 0x08 * bits[109]; pulse[1][3] = 0x01 * bits[146] + 0x02 * bits[147] + 0x04 * bits[148] + 0x08 * bits[113]; pulse[1][4] = 0x01 * bits[149] + 0x02 * bits[150] + 0x04 * bits[151] + 0x08 * bits[117]; pulse[1][5] = 0x01 * bits[197] + 0x02 * bits[198] + 0x04 * bits[199]; pulse[1][6] = 0x01 * bits[200] + 0x02 * bits[201] + 0x04 * bits[202]; pulse[1][7] = 0x01 * bits[203] + 0x02 * bits[204] + 0x04 * bits[205]; pulse[1][8] = 0x01 * bits[206] + 0x02 * bits[207] + 0x04 * bits[208]; pulse[1][9] = 0x01 * bits[209] + 0x02 * bits[210] + 0x04 * bits[211]; pulse[2][0] = 0x01 * bits[152] + 0x02 * bits[153] + 0x04 * bits[154] + 0x08 * bits[98]; pulse[2][1] = 0x01 * bits[155] + 0x02 * bits[156] + 0x04 * bits[157] + 0x08 * bits[102]; pulse[2][2] = 0x01 * bits[158] + 0x02 * bits[159] + 0x04 * bits[160] + 0x08 * bits[110]; pulse[2][3] = 0x01 * bits[161] + 0x02 * bits[162] + 0x04 * bits[163] + 0x08 * bits[114]; pulse[2][4] = 0x01 * bits[164] + 0x02 * bits[165] + 0x04 * bits[166] + 0x08 * bits[118]; pulse[2][5] = 0x01 * bits[212] + 0x02 * bits[213] + 0x04 * bits[214]; pulse[2][6] = 0x01 * bits[215] + 0x02 * bits[216] + 0x04 * bits[217]; pulse[2][7] = 0x01 * bits[218] + 0x02 * bits[219] + 0x04 * bits[220]; pulse[2][8] = 0x01 * bits[221] + 0x02 * bits[222] + 0x04 * bits[223]; pulse[2][9] = 0x01 * bits[224] + 0x02 * bits[225] + 0x04 * bits[226]; pulse[3][0] = 0x01 * bits[167] + 0x02 * bits[168] + 0x04 * bits[169] + 0x08 * bits[99]; pulse[3][1] = 0x01 * bits[170] + 0x02 * bits[171] + 0x04 * bits[172] + 0x08 * bits[103]; pulse[3][2] = 0x01 * bits[173] + 0x02 * bits[174] + 0x04 * bits[175] + 0x08 * bits[111]; pulse[3][3] = 0x01 * bits[176] + 0x02 * bits[177] + 0x04 * bits[178] + 0x08 * bits[115]; pulse[3][4] = 0x01 * bits[179] + 0x02 * bits[180] + 0x04 * bits[181] + 0x08 * bits[119]; pulse[3][5] = 0x01 * bits[227] + 0x02 * bits[228] + 0x04 * bits[229]; pulse[3][6] = 0x01 * bits[230] + 0x02 * bits[231] + 0x04 * bits[232]; pulse[3][7] = 0x01 * bits[233] + 0x02 * bits[234] + 0x04 * bits[235]; pulse[3][8] = 0x01 * bits[236] + 0x02 * bits[237] + 0x04 * bits[238]; pulse[3][9] = 0x01 * bits[239] + 0x02 * bits[240] + 0x04 * bits[241]; } // Block size in bytes for each of the 16 frame types, not // counting the initial byte that indicates the frame type. // Can be used to skip over unsupported frame types. static private int BLOCK_SIZES[] = { 12, 13, 15, 17, 19, 20, 26, 31, 5, 0, 0, 0, 0, 0, 0, 0 }; static private int GAIN_FAC_MR515[] = { 28753, 2785, 6594, 7413, 10444, 1269, 4423, 1556, 12820, 2498, 4833, 2498, 7864, 1884, 3153, 1802, 20193, 3031, 5857, 4014, 8970, 1392, 4096, 655, 13926, 3112, 4669, 2703, 6553, 901, 2662, 655, 23511, 2457, 5079, 4096, 8560, 737, 4259, 2088, 12288, 1474, 4628, 1433, 7004, 737, 2252, 1228, 17326, 2334, 5816, 3686, 8601, 778, 3809, 614, 9256, 1761, 3522, 1966, 5529, 737, 3194, 778 }; static private int QUA_ENER_MR515[] = { 17333, -3431, 4235, 5276, 8325, -10422, 683, -8609, 10148, -4398, 1472, -4398, 5802, -6907, -2327, -7303, 14189, -2678, 3181, -180, 6972, -9599, 0, -16305, 10884, -2444, 1165, -3697, 4180, -13468, -3833, -16305, 15543, -4546, 1913, 0, 6556, -15255, 347, -5993, 9771, -9090, 1086, -9341, 4772, -15255, -5321, -10714, 12827, -5002, 3118, -938, 6598, -14774, -646, -16879, 7251, -7508, -1343, -6529, 2668, -15255, -2212, -2454, -14774 }; static private int QUA_GAIN_CODE[] = { 159, -3776, -22731, 206, -3394, -20428, 268, -3005, -18088, 349, -2615, -15739, 419, -2345, -14113, 482, -2138, -12867, 554, -1932, -11629, 637, -1726, -10387, 733, -1518, -9139, 842, -1314, -7906, 969, -1106, -6656, 1114, -900, -5416, 1281, -694, -4173, 1473, -487, -2931, 1694, -281, -1688, 1948, -75, -445, 2241, 133, 801, 2577, 339, 2044, 2963, 545, 3285, 3408, 752, 4530, 3919, 958, 5772, 4507, 1165, 7016, 5183, 1371, 8259, 5960, 1577, 9501, 6855, 1784, 10745, 7883, 1991, 11988, 9065, 2197, 13231, 10425, 2404, 14474, 12510, 2673, 16096, 16263, 3060, 18429, 21142, 3448, 20763, 27485, 3836, 23097}; static private int GAIN_FAC_MR475[] = { 812, 128, 542, 140, 2873, 1135, 2266, 3402, 2067, 563, 12677, 647, 4132, 1798, 5601, 5285, 7689, 374, 3735, 441, 10912, 2638, 11807, 2494, 20490, 797, 5218, 675, 6724, 8354, 5282, 1696, 1488, 428, 5882, 452, 5332, 4072, 3583, 1268, 2469, 901, 15894, 1005, 14982, 3271, 10331, 4858, 3635, 2021, 2596, 835, 12360, 4892, 12206, 1704, 13432, 1604, 9118, 2341, 3968, 1538, 5479, 9936, 3795, 417, 1359, 414, 3640, 1569, 7995, 3541, 11405, 645, 8552, 635, 4056, 1377, 16608, 6124, 11420, 700, 2007, 607, 12415, 1578, 11119, 4654, 13680, 1708, 11990, 1229, 7996, 7297, 13231, 5715, 2428, 1159, 2073, 1941, 6218, 6121, 3546, 1804, 8925, 1802, 8679, 1580, 13935, 3576, 13313, 6237, 6142, 1130, 5994, 1734, 14141, 4662, 11271, 3321, 12226, 1551, 13931, 3015, 5081, 10464, 9444, 6706, 1689, 683, 1436, 1306, 7212, 3933, 4082, 2713, 7793, 704, 15070, 802, 6299, 5212, 4337, 5357, 6676, 541, 6062, 626, 13651, 3700, 11498, 2408, 16156, 716, 12177, 751, 8065, 11489, 6314, 2256, 4466, 496, 7293, 523, 10213, 3833, 8394, 3037, 8403, 966, 14228, 1880, 8703, 5409, 16395, 4863, 7420, 1979, 6089, 1230, 9371, 4398, 14558, 3363, 13559, 2873, 13163, 1465, 5534, 1678, 13138, 14771, 7338, 600, 1318, 548, 4252, 3539, 10044, 2364, 10587, 622, 13088, 669, 14126, 3526, 5039, 9784, 15338, 619, 3115, 590, 16442, 3013, 15542, 4168, 15537, 1611, 15405, 1228, 16023, 9299, 7534, 4976, 1990, 1213, 11447, 1157, 12512, 5519, 9475, 2644, 7716, 2034, 13280, 2239, 16011, 5093, 8066, 6761, 10083, 1413, 5002, 2347, 12523, 5975, 15126, 2899, 18264, 2289, 15827, 2527, 16265, 10254, 14651, 11319, 1797, 337, 3115, 397, 3510, 2928, 4592, 2670, 7519, 628, 11415, 656, 5946, 2435, 6544, 7367, 8238, 829, 4000, 863, 10032, 2492, 16057, 3551, 18204, 1054, 6103, 1454, 5884, 7900, 18752, 3468, 1864, 544, 9198, 683, 11623, 4160, 4594, 1644, 3158, 1157, 15953, 2560, 12349, 3733, 17420, 5260, 6106, 2004, 2917, 1742, 16467, 5257, 16787, 1680, 17205, 1759, 4773, 3231, 7386, 6035, 14342, 10012, 4035, 442, 4194, 458, 9214, 2242, 7427, 4217, 12860, 801, 11186, 825, 12648, 2084, 12956, 6554, 9505, 996, 6629, 985, 10537, 2502, 15289, 5006, 12602, 2055, 15484, 1653, 16194, 6921, 14231, 5790, 2626, 828, 5615, 1686, 13663, 5778, 3668, 1554, 11313, 2633, 9770, 1459, 14003, 4733, 15897, 6291, 6278, 1870, 7910, 2285, 16978, 4571, 16576, 3849, 15248, 2311, 16023, 3244, 14459, 17808, 11847, 2763, 1981, 1407, 1400, 876, 4335, 3547, 4391, 4210, 5405, 680, 17461, 781, 6501, 5118, 8091, 7677, 7355, 794, 8333, 1182, 15041, 3160, 14928, 3039, 20421, 880, 14545, 852, 12337, 14708, 6904, 1920, 4225, 933, 8218, 1087, 10659, 4084, 10082, 4533, 2735, 840, 20657, 1081, 16711, 5966, 15873, 4578, 10871, 2574, 3773, 1166, 14519, 4044, 20699, 2627, 15219, 2734, 15274, 2186, 6257, 3226, 13125, 19480, 7196, 930, 2462, 1618, 4515, 3092, 13852, 4277, 10460, 833, 17339, 810, 16891, 2289, 15546, 8217, 13603, 1684, 3197, 1834, 15948, 2820, 15812, 5327, 17006, 2438, 16788, 1326, 15671, 8156, 11726, 8556, 3762, 2053, 9563, 1317, 13561, 6790, 12227, 1936, 8180, 3550, 13287, 1778, 16299, 6599, 16291, 7758, 8521, 2551, 7225, 2645, 18269, 7489, 16885, 2248, 17882, 2884, 17265, 3328, 9417, 20162, 11042, 8320, 1286, 620, 1431, 583, 5993, 2289, 3978, 3626, 5144, 752, 13409, 830, 5553, 2860, 11764, 5908, 10737, 560, 5446, 564, 13321, 3008, 11946, 3683, 19887, 798, 9825, 728, 13663, 8748, 7391, 3053, 2515, 778, 6050, 833, 6469, 5074, 8305, 2463, 6141, 1865, 15308, 1262, 14408, 4547, 13663, 4515, 3137, 2983, 2479, 1259, 15088, 4647, 15382, 2607, 14492, 2392, 12462, 2537, 7539, 2949, 12909, 12060, 5468, 684, 3141, 722, 5081, 1274, 12732, 4200, 15302, 681, 7819, 592, 6534, 2021, 16478, 8737, 13364, 882, 5397, 899, 14656, 2178, 14741, 4227, 14270, 1298, 13929, 2029, 15477, 7482, 15815, 4572, 2521, 2013, 5062, 1804, 5159, 6582, 7130, 3597, 10920, 1611, 11729, 1708, 16903, 3455, 16268, 6640, 9306, 1007, 9369, 2106, 19182, 5037, 12441, 4269, 15919, 1332, 15357, 3512, 11898, 14141, 16101, 6854, 2010, 737, 3779, 861, 11454, 2880, 3564, 3540, 9057, 1241, 12391, 896, 8546, 4629, 11561, 5776, 8129, 589, 8218, 588, 18728, 3755, 12973, 3149, 15729, 758, 16634, 754, 15222, 11138, 15871, 2208, 4673, 610, 10218, 678, 15257, 4146, 5729, 3327, 8377, 1670, 19862, 2321, 15450, 5511, 14054, 5481, 5728, 2888, 7580, 1346, 14384, 5325, 16236, 3950, 15118, 3744, 15306, 1435, 14597, 4070, 12301, 15696, 7617, 1699, 2170, 884, 4459, 4567, 18094, 3306, 12742, 815, 14926, 907, 15016, 4281, 15518, 8368, 17994, 1087, 2358, 865, 16281, 3787, 15679, 4596, 16356, 1534, 16584, 2210, 16833, 9697, 15929, 4513, 3277, 1085, 9643, 2187, 11973, 6068, 9199, 4462, 8955, 1629, 10289, 3062, 16481, 5155, 15466, 7066, 13678, 2543, 5273, 2277, 16746, 6213, 16655, 3408, 20304, 3363, 18688, 1985, 14172, 12867, 15154, 15703, 4473, 1020, 1681, 886, 4311, 4301, 8952, 3657, 5893, 1147, 11647, 1452, 15886, 2227, 4582, 6644, 6929, 1205, 6220, 799, 12415, 3409, 15968, 3877, 19859, 2109, 9689, 2141, 14742, 8830, 14480, 2599, 1817, 1238, 7771, 813, 19079, 4410, 5554, 2064, 3687, 2844, 17435, 2256, 16697, 4486, 16199, 5388, 8028, 2763, 3405, 2119, 17426, 5477, 13698, 2786, 19879, 2720, 9098, 3880, 18172, 4833, 17336, 12207, 5116, 996, 4935, 988, 9888, 3081, 6014, 5371, 15881, 1667, 8405, 1183, 15087, 2366, 19777, 7002, 11963, 1562, 7279, 1128, 16859, 1532, 15762, 5381, 14708, 2065, 20105, 2155, 17158, 8245, 17911, 6318, 5467, 1504, 4100, 2574, 17421, 6810, 5673, 2888, 16636, 3382, 8975, 1831, 20159, 4737, 19550, 7294, 6658, 2781, 11472, 3321, 19397, 5054, 18878, 4722, 16439, 2373, 20430, 4386, 11353, 26526, 11593, 3068, 2866, 1566, 5108, 1070, 9614, 4915, 4939, 3536, 7541, 878, 20717, 851, 6938, 4395, 16799, 7733, 10137, 1019, 9845, 964, 15494, 3955, 15459, 3430, 18863, 982, 20120, 963, 16876, 12887, 14334, 4200, 6599, 1220, 9222, 814, 16942, 5134, 5661, 4898, 5488, 1798, 20258, 3962, 17005, 6178, 17929, 5929, 9365, 3420, 7474, 1971, 19537, 5177, 19003, 3006, 16454, 3788, 16070, 2367, 8664, 2743, 9445, 26358, 10856, 1287, 3555, 1009, 5606, 3622, 19453, 5512, 12453, 797, 20634, 911, 15427, 3066, 17037, 10275, 18883, 2633, 3913, 1268, 19519, 3371, 18052, 5230, 19291, 1678, 19508, 3172, 18072, 10754, 16625, 6845, 3134, 2298, 10869, 2437, 15580, 6913, 12597, 3381, 11116, 3297, 16762, 2424, 18853, 6715, 17171, 9887, 12743, 2605, 8937, 3140, 19033, 7764, 18347, 3880, 20475, 3682, 19602, 3380, 13044, 19373, 10526, 23124}; static private int GRAY[] = {0, 1, 3, 2, 5, 6, 4, 7}; static private int QUA_GAIN_PITCH[] = { 0, 3277, 6556, 8192, 9830, 11469, 12288, 13107, 13926, 14746, 15565, 16384, 17203, 18022, 18842, 19661}; /** For debugging public static void main(String[] argv) throws Exception { File f = new File(""); CheapAMR c = new CheapAMR(); c.ReadFile(f); c.WriteFile(new File(""), 0, c.getNumFrames()); } **/};
CheapMP3
/* * 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.hwtt.soundfile;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;/** * CheapMP3 represents an MP3 file by doing a "cheap" scan of the file, parsing * the frame headers only and getting an extremely rough estimate of the volume * level of each frame. * * TODO: Useful unit tests might be to look for sync in various places: FF FA FF * FB 00 FF FA FF FF FA ([ 00 ] * 12) FF FA ([ 00 ] * 13) FF FA */public class CheapMP3 extends CheapSoundFile { public static Factory getFactory() { return new Factory() { public CheapSoundFile create() { return new CheapMP3(); } public String[] getSupportedExtensions() { return new String[] { "mp3" }; } }; } // Member variables representing frame data private int mNumFrames; private int[] mFrameOffsets; private int[] mFrameLens; private int[] mFrameGains; private int mFileSize; private int mAvgBitRate; private int mGlobalSampleRate; private int mGlobalChannels; // Member variables used during initialization private int mMaxFrames; private int mBitrateSum; private int mMinGain; private int mMaxGain; public CheapMP3() { } public int getNumFrames() { return mNumFrames; } public int[] getFrameOffsets() { return mFrameOffsets; } public int getSamplesPerFrame() { return 1152; } public int[] getFrameLens() { return mFrameLens; } public int[] getFrameGains() { return mFrameGains; } public int getFileSizeBytes() { return mFileSize; } public int getAvgBitrateKbps() { return mAvgBitRate; } public int getSampleRate() { return mGlobalSampleRate; } public int getChannels() { return mGlobalChannels; } public String getFiletype() { return "MP3"; } /** * MP3 supports seeking into the middle of the file, no header needed, so * this method is supported to hear exactly what a "cut" of the file sounds * like without needing to actually save a file to disk first. */ public int getSeekableFrameOffset(int frame) { if (frame <= 0) { return 0; } else if (frame >= mNumFrames) { return mFileSize; } else { return mFrameOffsets[frame]; } } public void ReadFile(File inputFile) throws java.io.FileNotFoundException, java.io.IOException { super.ReadFile(inputFile); mNumFrames = 0; mMaxFrames = 64; // This will grow as needed mFrameOffsets = new int[mMaxFrames]; mFrameLens = new int[mMaxFrames]; mFrameGains = new int[mMaxFrames]; mBitrateSum = 0; mMinGain = 255; mMaxGain = 0; // No need to handle filesizes larger than can fit in a 32-bit int mFileSize = (int) mInputFile.length(); FileInputStream stream = new FileInputStream(mInputFile); int pos = 0; int offset = 0; byte[] buffer = new byte[12]; while (pos < mFileSize - 12) { // Read 12 bytes at a time and look for a sync code (0xFF) while (offset < 12) { offset += stream.read(buffer, offset, 12 - offset); } int bufferOffset = 0; while (bufferOffset < 12 && buffer[bufferOffset] != -1) bufferOffset++; if (mProgressListener != null) { boolean keepGoing = mProgressListener.reportProgress(pos * 1.0 / mFileSize); if (!keepGoing) { break; } } if (bufferOffset > 0) { // We didn't find a sync code (0xFF) at position 0; // shift the buffer over and try again for (int i = 0; i < 12 - bufferOffset; i++) buffer[i] = buffer[bufferOffset + i]; pos += bufferOffset; offset = 12 - bufferOffset; continue; } // Check for MPEG 1 Layer III or MPEG 2 Layer III codes int mpgVersion = 0; if (buffer[1] == -6 || buffer[1] == -5) { mpgVersion = 1; } else if (buffer[1] == -14 || buffer[1] == -13) { mpgVersion = 2; } else { bufferOffset = 1; for (int i = 0; i < 12 - bufferOffset; i++) buffer[i] = buffer[bufferOffset + i]; pos += bufferOffset; offset = 12 - bufferOffset; continue; } // The third byte has the bitrate and samplerate int bitRate; int sampleRate; if (mpgVersion == 1) { // MPEG 1 Layer III bitRate = BITRATES_MPEG1_L3[(buffer[2] & 0xF0) >> 4]; sampleRate = SAMPLERATES_MPEG1_L3[(buffer[2] & 0x0C) >> 2]; } else { // MPEG 2 Layer III bitRate = BITRATES_MPEG2_L3[(buffer[2] & 0xF0) >> 4]; sampleRate = SAMPLERATES_MPEG2_L3[(buffer[2] & 0x0C) >> 2]; } if (bitRate == 0 || sampleRate == 0) { bufferOffset = 2; for (int i = 0; i < 12 - bufferOffset; i++) buffer[i] = buffer[bufferOffset + i]; pos += bufferOffset; offset = 12 - bufferOffset; continue; } // From here on we assume the frame is good mGlobalSampleRate = sampleRate; int padding = (buffer[2] & 2) >> 1; int frameLen = 144 * bitRate * 1000 / sampleRate + padding; int gain; if ((buffer[3] & 0xC0) == 0xC0) { // 1 channel mGlobalChannels = 1; if (mpgVersion == 1) { gain = ((buffer[10] & 0x01) << 7) + ((buffer[11] & 0xFE) >> 1); } else { gain = ((buffer[9] & 0x03) << 6) + ((buffer[10] & 0xFC) >> 2); } } else { // 2 channels mGlobalChannels = 2; if (mpgVersion == 1) { gain = ((buffer[9] & 0x7F) << 1) + ((buffer[10] & 0x80) >> 7); } else { gain = 0; // ??? } } mBitrateSum += bitRate; mFrameOffsets[mNumFrames] = pos; mFrameLens[mNumFrames] = frameLen; mFrameGains[mNumFrames] = gain; if (gain < mMinGain) mMinGain = gain; if (gain > mMaxGain) mMaxGain = gain; mNumFrames++; if (mNumFrames == mMaxFrames) { // We need to grow our arrays. Rather than naively // doubling the array each time, we estimate the exact // number of frames we need and add 10% padding. In // practice this seems to work quite well, only one // resize is ever needed, however to avoid pathological // cases we make sure to always double the size at a minimum. mAvgBitRate = mBitrateSum / mNumFrames; int totalFramesGuess = ((mFileSize / mAvgBitRate) * sampleRate) / 144000; int newMaxFrames = totalFramesGuess * 11 / 10; if (newMaxFrames < mMaxFrames * 2) newMaxFrames = mMaxFrames * 2; int[] newOffsets = new int[newMaxFrames]; int[] newLens = new int[newMaxFrames]; int[] newGains = new int[newMaxFrames]; for (int i = 0; i < mNumFrames; i++) { newOffsets[i] = mFrameOffsets[i]; newLens[i] = mFrameLens[i]; newGains[i] = mFrameGains[i]; } mFrameOffsets = newOffsets; mFrameLens = newLens; mFrameGains = newGains; mMaxFrames = newMaxFrames; } stream.skip(frameLen - 12); pos += frameLen; offset = 0; } // We're done reading the file, do some postprocessing if (mNumFrames > 0) mAvgBitRate = mBitrateSum / mNumFrames; else mAvgBitRate = 0; } public void WriteFile(File outputFile, int startFrame, int numFrames) throws java.io.IOException { outputFile.createNewFile(); FileInputStream in = new FileInputStream(mInputFile); FileOutputStream out = new FileOutputStream(outputFile); int maxFrameLen = 0; for (int i = 0; i < numFrames; i++) { if (mFrameLens[startFrame + i] > maxFrameLen) maxFrameLen = mFrameLens[startFrame + i]; } byte[] buffer = new byte[maxFrameLen]; int pos = 0; for (int i = 0; i < numFrames; i++) { int skip = mFrameOffsets[startFrame + i] - pos; int len = mFrameLens[startFrame + i]; if (skip > 0) { in.skip(skip); pos += skip; } in.read(buffer, 0, len); out.write(buffer, 0, len); pos += len; } in.close(); out.close(); } public void WriteFile(FileOutputStream outputFile, int startFrame, int numFrames) throws java.io.IOException { FileInputStream in = new FileInputStream(mInputFile); FileOutputStream out = outputFile; int maxFrameLen = 0; for (int i = 0; i < numFrames; i++) { if (mFrameLens[startFrame + i] > maxFrameLen) maxFrameLen = mFrameLens[startFrame + i]; } byte[] buffer = new byte[maxFrameLen]; int pos = 0; for (int i = 0; i < numFrames; i++) { int skip = mFrameOffsets[startFrame + i] - pos; int len = mFrameLens[startFrame + i]; if (skip > 0) { in.skip(skip); pos += skip; } in.read(buffer, 0, len); out.write(buffer, 0, len); pos += len; } in.close(); out.close(); } static private int BITRATES_MPEG1_L3[] = { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 }; static private int BITRATES_MPEG2_L3[] = { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }; static private int SAMPLERATES_MPEG1_L3[] = { 44100, 48000, 32000, 0 }; static private int SAMPLERATES_MPEG2_L3[] = { 22050, 24000, 16000, 0 };};
CheapSoundFile
/* * 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.hwtt.soundfile;import java.io.File;import java.io.FileInputStream;import java.io.InputStream;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 ArrayListsSupportedExtensions = new ArrayList (); static HashMap sExtensionMap = new HashMap (); 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(); System.out.println("name-->"+name); String[] components = name.split("\\."); for(int i=0;i "+components[i]); } 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 ReadStrem(InputStream inputStream) throws java.io.IOException { } 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 { }};
CheapWAV
/* * 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.hwtt.soundfile;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.InputStream;/** * CheapWAV represents a standard 16-bit WAV file, splitting it into * artificial frames of 20 ms and taking the maximum of each frame to * get an approximation of the waveform contour. */public class CheapWAV extends CheapSoundFile { public static Factory getFactory() { return new Factory() { public CheapSoundFile create() { return new CheapWAV(); } public String[] getSupportedExtensions() { return new String[] { "wav" }; } }; } // Member variables containing frame info private int mNumFrames; private int[] mFrameOffsets; private int[] mFrameLens; private int[] mFrameGains; private int mFrameBytes; private int mFileSize; private int mSampleRate; private int mChannels; // Member variables used during initialization private int mOffset; public CheapWAV() { } public int getNumFrames() { return mNumFrames; } public int getSamplesPerFrame() { return mSampleRate / 50; } public int[] getFrameOffsets() { return mFrameOffsets; } public int[] getFrameLens() { return mFrameLens; } public int[] getFrameGains() { return mFrameGains; } public int getFileSizeBytes() { return mFileSize; } public int getAvgBitrateKbps() { return mSampleRate * mChannels * 2 / 1024; } public int getSampleRate() { return mSampleRate; } public int getChannels() { return mChannels; } public String getFiletype() { return "WAV"; } public void ReadFile(File inputFile) throws java.io.FileNotFoundException, java.io.IOException { super.ReadFile(inputFile); mFileSize = (int)mInputFile.length(); if (mFileSize < 128) { throw new java.io.IOException("File too small to parse"); } FileInputStream stream = new FileInputStream(mInputFile); byte[] header = new byte[12]; stream.read(header, 0, 12); mOffset += 12; if (header[0] != 'R' || header[1] != 'I' || header[2] != 'F' || header[3] != 'F' || header[8] != 'W' || header[9] != 'A' || header[10] != 'V' || header[11] != 'E') { throw new java.io.IOException("Not a WAV file"); } mChannels = 0; mSampleRate = 0; while (mOffset + 8 <= mFileSize) { byte[] chunkHeader = new byte[8]; stream.read(chunkHeader, 0, 8); mOffset += 8; int chunkLen = ((0xff & chunkHeader[7]) << 24) | ((0xff & chunkHeader[6]) << 16) | ((0xff & chunkHeader[5]) << 8) | ((0xff & chunkHeader[4])); if (chunkHeader[0] == 'f' && chunkHeader[1] == 'm' && chunkHeader[2] == 't' && chunkHeader[3] == ' ') { if (chunkLen < 16 || chunkLen > 1024) { throw new java.io.IOException( "WAV file has bad fmt chunk"); } byte[] fmt = new byte[chunkLen]; stream.read(fmt, 0, chunkLen); mOffset += chunkLen; int format = ((0xff & fmt[1]) << 8) | ((0xff & fmt[0])); mChannels = ((0xff & fmt[3]) << 8) | ((0xff & fmt[2])); mSampleRate = ((0xff & fmt[7]) << 24) | ((0xff & fmt[6]) << 16) | ((0xff & fmt[5]) << 8) | ((0xff & fmt[4])); if (format != 1) { throw new java.io.IOException( "Unsupported WAV file encoding"); } } else if (chunkHeader[0] == 'd' && chunkHeader[1] == 'a' && chunkHeader[2] == 't' && chunkHeader[3] == 'a') { if (mChannels == 0 || mSampleRate == 0) { throw new java.io.IOException( "Bad WAV file: data chunk before fmt chunk"); } int frameSamples = (mSampleRate * mChannels) / 50; mFrameBytes = frameSamples * 2; mNumFrames = (chunkLen + (mFrameBytes - 1)) / mFrameBytes; mFrameOffsets = new int[mNumFrames]; mFrameLens = new int[mNumFrames]; mFrameGains = new int[mNumFrames]; byte[] oneFrame = new byte[mFrameBytes]; int i = 0; int frameIndex = 0; while (i < chunkLen) { int oneFrameBytes = mFrameBytes; if (i + oneFrameBytes > chunkLen) { i = chunkLen - oneFrameBytes; } stream.read(oneFrame, 0, oneFrameBytes); int maxGain = 0; for (int j = 1; j < oneFrameBytes; j += 4 * mChannels) { int val = java.lang.Math.abs(oneFrame[j]); if (val > maxGain) { maxGain = val; } } mFrameOffsets[frameIndex] = mOffset; mFrameLens[frameIndex] = oneFrameBytes; mFrameGains[frameIndex] = maxGain; frameIndex++; mOffset += oneFrameBytes; i += oneFrameBytes; if (mProgressListener != null) { boolean keepGoing = mProgressListener.reportProgress( i * 1.0 / chunkLen); if (!keepGoing) { break; } } } } else { stream.skip(chunkLen); mOffset += chunkLen; } } } public void WriteFile(File outputFile, int startFrame, int numFrames) throws java.io.IOException { outputFile.createNewFile(); FileInputStream in = new FileInputStream(mInputFile); FileOutputStream out = new FileOutputStream(outputFile); long totalAudioLen = 0; for (int i = 0; i < numFrames; i++) { totalAudioLen += mFrameLens[startFrame + i]; } long totalDataLen = totalAudioLen + 36; long longSampleRate = mSampleRate; long byteRate = mSampleRate * 2 * mChannels; byte[] header = new byte[44]; header[0] = 'R'; // RIFF/WAVE header header[1] = 'I'; header[2] = 'F'; header[3] = 'F'; header[4] = (byte) (totalDataLen & 0xff); header[5] = (byte) ((totalDataLen >> 8) & 0xff); header[6] = (byte) ((totalDataLen >> 16) & 0xff); header[7] = (byte) ((totalDataLen >> 24) & 0xff); header[8] = 'W'; header[9] = 'A'; header[10] = 'V'; header[11] = 'E'; header[12] = 'f'; // 'fmt ' chunk header[13] = 'm'; header[14] = 't'; header[15] = ' '; header[16] = 16; // 4 bytes: size of 'fmt ' chunk header[17] = 0; header[18] = 0; header[19] = 0; header[20] = 1; // format = 1 header[21] = 0; header[22] = (byte) mChannels; header[23] = 0; header[24] = (byte) (longSampleRate & 0xff); header[25] = (byte) ((longSampleRate >> 8) & 0xff); header[26] = (byte) ((longSampleRate >> 16) & 0xff); header[27] = (byte) ((longSampleRate >> 24) & 0xff); header[28] = (byte) (byteRate & 0xff); header[29] = (byte) ((byteRate >> 8) & 0xff); header[30] = (byte) ((byteRate >> 16) & 0xff); header[31] = (byte) ((byteRate >> 24) & 0xff); header[32] = (byte) (2 * mChannels); // block align header[33] = 0; header[34] = 16; // bits per sample header[35] = 0; header[36] = 'd'; header[37] = 'a'; header[38] = 't'; header[39] = 'a'; header[40] = (byte) (totalAudioLen & 0xff); header[41] = (byte) ((totalAudioLen >> 8) & 0xff); header[42] = (byte) ((totalAudioLen >> 16) & 0xff); header[43] = (byte) ((totalAudioLen >> 24) & 0xff); out.write(header, 0, 44); byte[] buffer = new byte[mFrameBytes]; int pos = 0; for (int i = 0; i < numFrames; i++) { int skip = mFrameOffsets[startFrame + i] - pos; int len = mFrameLens[startFrame + i]; if (skip < 0) { continue; } if (skip > 0) { in.skip(skip); pos += skip; } in.read(buffer, 0, len); out.write(buffer, 0, len); pos += len; } in.close(); out.close(); }};
(在F:\java\ringdemo)