音乐的裁剪



首先为了方便音乐的截取,先把MediaPlayer封装,代码如下:

import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;

public class MusicPlayer implements OnCompletionListener{
	private MediaPlayer mPlayer ;
	private Handler mHander;
	private onMusicProgressChangedListener listener;
	private boolean isPlaying;
	private int maxValue;
	private int minValue;

	public MusicPlayer(Looper mainLooper) {
		super();
		mPlayer = new MediaPlayer();
		mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
		mPlayer.setOnCompletionListener(this);
		
		mHander = new Handler(mainLooper) {
			@Override
			public void handleMessage(Message msg) {
				int progress = mPlayer.getCurrentPosition();
				if (maxValue > 0 && progress > maxValue) {
					mPlayer.seekTo(minValue);
					progress = minValue;
				}
				if (listener != null) {
					listener.onMusicProgressChanged(progress);
					mHander.sendEmptyMessageDelayed(0, 1000);
				}
			}
		};
	}
	//获取录音路径
	public boolean setSource(String path) {
		try {
			mPlayer.reset();
			mPlayer.setDataSource(path);
			mPlayer.prepare();
		} catch (Exception e) {
			return false;
		}
		return true;
	}
	//暂停播放
	public void pause() {
		isPlaying = false;
		mPlayer.pause();
		mHander.removeMessages(0);
	}
	//停止播放
	public void stop() {
		isPlaying = false;
		mPlayer.stop();
		mHander.removeMessages(0);
	}
	//开始播放
	public void start() {
		isPlaying = true;
		mPlayer.start();
		mHander.sendEmptyMessage(0);
	}
	//清空
	public void release() {
		mPlayer.release();
	}
	//传递进度
	public void setonMusicProgressChangedListener(onMusicProgressChangedListener listener) {
		this.listener = listener;
	}
	//播放完成
	@Override
	public void onCompletion(MediaPlayer mp) {
		mHander.removeMessages(0);
		if (isPlaying) {
			mPlayer.seekTo(minValue);
			start();
		}
	}
	//传递进度接口
	public interface onMusicProgressChangedListener {
		void onMusicProgressChanged(int progress);
	}
	//是否在播放
	public boolean isPlaying() {
		return isPlaying;
	}
	//定点播放
	public void seekTo(int progress) {
		mPlayer.seekTo(progress);
	}
	//获取滑块的最大值
	public void setMaxValue(int maxValue) {
		this.maxValue = maxValue;
	}
	//获取滑块的最小值
	public void setMinValue(int minValue) {
		this.minValue = minValue;
		mPlayer.seekTo(minValue);
		start();
	}
	
}

在自定义一个Dialog代码如下:

import android.app.AlertDialog;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;


public class MarkerDialog extends AlertDialog implements View.OnClickListener {
	private TextView mTitleText, mContentText;
	private LinearLayout mContentParent;
	private Button mConfirm, mCancel;
	private OnClickListener mConfirmListener, mCancelListener;
	private Context context;
	public MarkerDialog(Context context) {
		super(context, THEME_HOLO_LIGHT);
		this.context = context;
	}

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		LayoutParams params = getWindow().getAttributes();
		params.height = LayoutParams.WRAP_CONTENT;
		WindowManager wm = (WindowManager)context 
				.getSystemService(Context.WINDOW_SERVICE);
		int screenWidth = wm.getDefaultDisplay().getWidth();;
		float scale;
		if (screenWidth < 500) {
			scale = 0.96F;
		} else if (screenWidth < 900) {
			scale = 0.9F;
		} else {
			scale = 0.9F;
		}
		params.width = (int) (screenWidth * scale);
		params.dimAmount = 0.2F;
		getWindow().setAttributes(params);
		setContentView(R.layout.dialog_base);
		mTitleText = (TextView) findViewById(R.id.dialog_title);
		mContentText = (TextView) findViewById(R.id.dialog_content_text);
		mContentParent = (LinearLayout) findViewById(R.id.dialog_content);
		mConfirm = (Button) findViewById(R.id.dialog_button_yes);
		mConfirm.setOnClickListener(this);
		mCancel = (Button) findViewById(R.id.dialog_button_no);
		mCancel.setOnClickListener(this);

		setCanceledOnTouchOutside(false);
	}

	public void setConfirmListener(String button, OnClickListener listener) {
		mConfirmListener = listener;
		if (button != null) {
			mConfirm.setText(button);
		}
	}

	public void setCancelListener(String button, OnClickListener listener) {
		mCancelListener = listener;
		if (button != null) {
			mCancel.setText(button);
		}
	}

	@Override
	public void onClick(View v) {
		if (R.id.dialog_button_yes == v.getId()) {
			if (mConfirmListener == null || mConfirmListener.onClick(v)) {
				this.dismiss();
			}
		} else if (R.id.dialog_button_no == v.getId()) {
			if (mCancelListener == null || mCancelListener.onClick(v)) {
				this.dismiss();
			}
		}
	}

	public void setMarkerIcon(int id) {
		((ImageView) findViewById(R.id.dialog_icon)).setImageResource(id);
		;
	}

	public void setMarkerTitleDes(String des) {
		((TextView) findViewById(R.id.dialog_title_des)).setText(des);
	}

	public void setIconClickListener(View.OnClickListener listener) {
		findViewById(R.id.dialog_icon).setOnClickListener(listener);
	}

	public void setMarkerTitle(CharSequence title) {
		mTitleText.setText(title);
	}

	public void setMarkerTitle(int titleId) {
		setMarkerTitle(getContext().getString(titleId));
	}

	public void setMarkerMessage(CharSequence message) {
		mContentText.setVisibility(View.VISIBLE);
		mContentText.setText(message);
	}

	public void setMarkerMessage(int messageId) {
		setMarkerMessage(getContext().getString(messageId));
	}

	public void setMarkerView(View view) {
		mContentParent.addView(view);
	}

	public void setMarkerConfirmDrawable(int id) {
		mConfirm.setBackgroundResource(id);
	}

	public void hideButton(int button) {
		findViewById(R.id.button_divider).setVisibility(View.GONE);
		(button == BUTTON_POSITIVE ? mConfirm : mCancel)
				.setVisibility(View.GONE);
	}

	public interface OnClickListener {
		// 是否关闭dialog
		boolean onClick(View view);
	}
}

Dialog布局如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/dialog_background_color"
    android:orientation="vertical" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="50dip"
        android:orientation="horizontal" >

		<ImageView
		    android:id="@+id/dialog_icon"
		    android:layout_width="wrap_content"
		    android:layout_height="50dip"
		    android:layout_marginLeft="5dip"
		    android:contentDescription="@android:string/unknownName"
		    android:padding="5dip"
		    android:scaleType="centerInside" />

        <TextView
            android:id="@+id/dialog_title"
            android:layout_width="0dp"
            android:layout_weight="1"
            android:layout_height="50dip"
            android:gravity="center_vertical"
            android:layout_marginLeft="8dip"
            android:singleLine="true"
            android:textColor="@color/font_white_dialog"
            android:textSize="@dimen/font_size_big"
            android:textStyle="bold" />
        
        <TextView
            android:id="@+id/dialog_title_des"
            android:layout_width="wrap_content"
            android:layout_marginLeft="5dip"
            android:layout_marginRight="12dip"
            android:layout_height="50dip"
            android:gravity="center_vertical"
            android:singleLine="true"
            android:textColor="@color/font_gray"
            android:textSize="@dimen/font_size_big"
            android:padding="5dip" />
    </LinearLayout>

    <View
        android:layout_width="match_parent"
        android:layout_height="1dip"
        android:background="@color/font_gray" />

    <LinearLayout
        android:id="@+id/dialog_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="5dip"
        android:orientation="vertical"
        android:padding="5dip">

        <TextView
            android:id="@+id/dialog_content_text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_vertical"
            android:textColor="@color/font_black"
            android:textSize="@dimen/font_size_big"
            android:textStyle="bold"
            android:visibility="gone" />
    </LinearLayout>

    <View
        android:layout_width="match_parent"
        android:layout_height="1dip"
        android:background="@color/font_gray" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="50dip"
        android:orientation="horizontal" >

        <Button
            android:id="@+id/dialog_button_no"
            android:layout_width="0dip"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:background="@drawable/dialog_button_selector"
            android:gravity="center"
            android:text="@string/cancel"
            android:textColor="@color/font_white_dialog" />

        <View
            android:id="@+id/button_divider"
            android:layout_width="1dip"
            android:layout_height="match_parent"
            android:background="@color/font_gray" />

        <Button
            android:id="@+id/dialog_button_yes"
            android:layout_width="0dip"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:background="@drawable/dialog_button_selector"
            android:gravity="center"
            android:text="@string/confirm"
            android:textColor="@color/font_white_dialog" />
    </LinearLayout>

</LinearLayout>

Dailog中有添加了一个自定义View,代码如下:

public class MusicTrimView extends LinearLayout implements onMusicProgressChangedListener{
	private RangeSeekBar mBar;
	private TextView mStart, mEnd, mProgress;
	private onTimeChangedListener listener;
	private long minTime, maxTime;

	public MusicTrimView(Context context) {
		super(context);
		inflate(context, R.layout.dialog_trim_music, this);

		mStart = (TextView) findViewById(R.id.music_trim_start);
		mProgress = (TextView) findViewById(R.id.music_progress);
		mEnd = (TextView) findViewById(R.id.music_trim_end);

		mBar = (RangeSeekBar) findViewById(R.id.music_seekbar);
		mBar.setOnRangeSeekBarChangeListener(new OnRangeSeekBarChangeListener() {

			@Override
			public void onRangeSeekBarValuesChanged(RangeSeekBar bar,
					long minValue, long maxValue) {
				if (minValue != minTime) {
					if (listener != null) {
						listener.onMinTimeChanged(minValue);
					}
					minTime = minValue;
					mStart.setText(TimeUtils.formatAsMS(minValue));
				} else if (maxValue != maxTime) {
					if (listener != null) {
						listener.onMaxTimeChanged(maxValue);
					}
					maxTime = maxValue;
					mEnd.setText(TimeUtils.formatAsMS(maxValue));
				}
			}
		});
	}

	public void setProgress(int progress) {
		mBar.setProgress(progress);
		mProgress.setText(TimeUtils.formatAsMS(progress));
	}

	public void setMinTime(long minTime) {
		mBar.setSelectedMinValue(minTime);
		mStart.setText(TimeUtils.formatAsMS(minTime));
		this.minTime = minTime;
	}

	public void setMaxTime(long maxTime) {
		mBar.setSelectedMaxValue(maxTime);
		mEnd.setText(TimeUtils.formatAsMS(maxTime));
		this.maxTime = maxTime;
	}
	
	public void setDuration(long duration) {
		mBar.setMax(duration);
		mEnd.setText(TimeUtils.formatAsMS(duration));
		this.maxTime = duration;
	}

	public long getMinTime() {
		return mBar.getSelectedMinValue();
	}

	public long getMaxTime() {
		return mBar.getSelectedMaxValue();
	}
	//获取seekBar拖动的位置
	public void setonTimeChangedListener(onTimeChangedListener listener) {
		this.listener = listener;
	}

	public interface onTimeChangedListener {
		void onMinTimeChanged(long minTime);

		void onMaxTimeChanged(long maxTime);
	}

	@Override
	public void onMusicProgressChanged(int progress) {
		setProgress(progress);
	}
}

自定义View布局如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/music_trim"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:orientation="vertical"
    android:visibility="visible">

    <net.micode.soundrecorder.RangeSeekBar
        android:id="@+id/music_seekbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >

        <TextView
            android:id="@+id/music_trim_start"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_marginLeft="8dip"
            android:paddingBottom="4dip"
            android:paddingTop="4dip"
            android:text="00:00"
            android:textColor="@color/font_white_dialog" />

        <TextView
            android:id="@+id/music_progress"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:paddingBottom="4dip"
            android:paddingTop="4dip"
            android:text="00:00"
            android:textColor="@color/font_white_dialog" />

        <TextView
            android:id="@+id/music_trim_end"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_marginRight="8dip"
            android:paddingTop="4dip"
            android:text="03:00"
            android:textColor="@color/font_white_dialog" />

    </RelativeLayout>

</LinearLayout>

自定义View中又自定义了一个Seekbar,两边都能滑动的。代码如下:

public class RangeSeekBar extends ImageView {
	private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
	// private final Bitmap thumbImage = BitmapFactory.decodeResource(
	// getResources(), R.drawable.seek_thumb_normal);
	private final Bitmap thumbRightImage;
	// private final Bitmap thumbPressedImage = BitmapFactory.decodeResource(
	// getResources(), R.drawable.seek_thumb_pressed);
	private final Bitmap thumbLeftImage;

	private final Bitmap progressImage;
	private final float thumbWidth;
	private final float thumbHalfWidth;
	private final float thumbHalfHeight;
	private final float lineHeight;
	private final float padding;
	// private double absoluteMinValue, absoluteMaxValue;
	// private final NumberType numberType;
	private double absoluteMinValuePrim, absoluteMaxValuePrim = 1;
	private double normalizedMinValue = 0d;
	private double normalizedMaxValue = 1d;
	private Thumb pressedThumb = null;
	private boolean notifyWhileDragging = true;
	public final boolean IS_MULTI_COLORED;
	public final int SINGLE_COLOR;
	public final int LEFT_COLOR;
	public final int MIDDLE_COLOR;
	public final int RIGHT_COLOR;
	public final int BACKGROUND_COLOR;
	private OnRangeSeekBarChangeListener listener;

	private double progress;

	/**
	 * Default color of a {@link RangeSeekBar}, #FF33B5E5. This is also known as
	 * "Ice Cream Sandwich" blue.
	 */
	public static final int DEFAULT_COLOR = Color.argb(0xFF, 0x33, 0xB5, 0xE5);

	/**
	 * Callback listener interface to notify about changed range values.
	 * 
	 * @author Stephan Tittel (stephan.tittel@kom.tu-darmstadt.de)
	 * 
	 * @param The
	 *            Number type the RangeSeekBar has been declared with.
	 */
	public interface OnRangeSeekBarChangeListener {
		public void onRangeSeekBarValuesChanged(RangeSeekBar bar,
				long minValue, long maxValue);
	}

	/**
	 * Registers given listener callback to notify about changed selected
	 * values.
	 * 
	 * @param listener
	 *            The listener to notify about changed selected values.
	 */
	public void setOnRangeSeekBarChangeListener(
			OnRangeSeekBarChangeListener listener) {
		this.listener = listener;
	}

	/**
	 * An invalid pointer id.
	 */
	public static final int INVALID_POINTER_ID = 255;

	// Localized constants from MotionEvent for compatibility
	// with API < 8 "Froyo".
	public static final int ACTION_POINTER_UP = 0x6,
			ACTION_POINTER_INDEX_MASK = 0x0000ff00,
			ACTION_POINTER_INDEX_SHIFT = 8;

	private float mDownMotionX;
	private int mActivePointerId = INVALID_POINTER_ID;

	/**
	 * On touch, this offset plus the scaled value from the position of the
	 * touch will form the progress value. Usually 0.
	 */
	float mTouchProgressOffset;

	private int mScaledTouchSlop;
	private boolean mIsDragging;

	public RangeSeekBar(Context context, AttributeSet attrs) {
		super(context, attrs);
		IS_MULTI_COLORED = false;
		BACKGROUND_COLOR = Color.argb(0xFF, 0x00, 0x00, 0x00);
		SINGLE_COLOR = Color.argb(0xFF, 0x39, 0xab, 0xee);
		LEFT_COLOR = 0;
		MIDDLE_COLOR = 0;
		RIGHT_COLOR = 0;
		thumbRightImage = BitmapFactory.decodeResource(getResources(),
				R.drawable.trim_right);
		thumbLeftImage = BitmapFactory.decodeResource(getResources(),
				R.drawable.trim_left);
		progressImage = BitmapFactory.decodeResource(getResources(),
				R.drawable.seek_thumb);
		thumbWidth = thumbRightImage.getWidth();
		thumbHalfWidth = 0.5f * thumbWidth;
		thumbHalfHeight = 0.5f * thumbRightImage.getHeight();
		lineHeight = 0.3f * thumbHalfHeight;
		padding = thumbHalfWidth;

		// make RangeSeekBar focusable. This solves focus handling issues in
		// case EditText widgets are being used along with the RangeSeekBar
		// within ScollViews.
		setFocusable(true);
		setFocusableInTouchMode(true);
		mScaledTouchSlop = ViewConfiguration.get(getContext())
				.getScaledTouchSlop();
	}

	public boolean isNotifyWhileDragging() {
		return notifyWhileDragging;
	}

	/**
	 * Should the widget notify the listener callback while the user is still
	 * dragging a thumb? Default is false.
	 * 
	 * @param flag
	 */
	public void setNotifyWhileDragging(boolean flag) {
		this.notifyWhileDragging = flag;
	}

	/**
	 * Returns the currently selected min value.
	 * 
	 * @return The currently selected min value.
	 */
	public long getSelectedMinValue() {
		return (long) normalizedToValue(normalizedMinValue);
	}

	/**
	 * Sets the currently selected minimum value. The widget will be invalidated
	 * and redrawn.
	 * 
	 * @param value
	 *            The Number value to set the minimum value to. Will be clamped
	 *            to given absolute minimum/maximum range.
	 */
	public void setSelectedMinValue(double value) {
		// in case absoluteMinValue == absoluteMaxValue, avoid division by zero
		// when normalizing.
		if (0 == (absoluteMaxValuePrim - absoluteMinValuePrim)) {
			setNormalizedMinValue(0d);
		} else {
			setNormalizedMinValue(valueToNormalized(value));
		}
	}

	/**
	 * Returns the currently selected max value.
	 * 
	 * @return The currently selected max value.
	 */
	public long getSelectedMaxValue() {
		return (long) normalizedToValue(normalizedMaxValue);
	}

	/**
	 * Sets the currently selected maximum value. The widget will be invalidated
	 * and redrawn.
	 * 
	 * @param value
	 *            The Number value to set the maximum value to. Will be clamped
	 *            to given absolute minimum/maximum range.
	 */
	public void setSelectedMaxValue(double value) {
		// in case absoluteMinValue == absoluteMaxValue, avoid division by zero
		// when normalizing.
		if (0 == (absoluteMaxValuePrim - absoluteMinValuePrim)) {
			setNormalizedMaxValue(1d);
		} else {
			setNormalizedMaxValue(valueToNormalized(value));
		}
	}

	/**
	 * Handles thumb selection and movement. Notifies listener callback on
	 * certain events.
	 */
	@Override
	public boolean onTouchEvent(MotionEvent event) {

		if (!isEnabled())
			return false;

		int pointerIndex;

		final int action = event.getAction();
		switch (action & MotionEvent.ACTION_MASK) {

		case MotionEvent.ACTION_DOWN:
			// Remember where the motion event started
			mActivePointerId = event.getPointerId(event.getPointerCount() - 1);
			pointerIndex = event.findPointerIndex(mActivePointerId);
			mDownMotionX = event.getX(pointerIndex);

			pressedThumb = evalPressedThumb(mDownMotionX);

			// Only handle thumb presses.
			if (pressedThumb == null)
				return super.onTouchEvent(event);

			setPressed(true);
			invalidate();
			onStartTrackingTouch();
			trackTouchEvent(event);
			attemptClaimDrag();

			break;
		case MotionEvent.ACTION_MOVE:
			if (pressedThumb != null) {

				if (mIsDragging) {
					trackTouchEvent(event);
				} else {
					// Scroll to follow the motion event
					pointerIndex = event.findPointerIndex(mActivePointerId);
					final float x = event.getX(pointerIndex);

					if (Math.abs(x - mDownMotionX) > mScaledTouchSlop) {
						setPressed(true);
						invalidate();
						onStartTrackingTouch();
						trackTouchEvent(event);
						attemptClaimDrag();
					}
				}
				Log.i("info", getSelectedMinValue() + "-"
						+ getSelectedMaxValue());
				if (notifyWhileDragging && listener != null) {
					listener.onRangeSeekBarValuesChanged(this,
							(long) getSelectedMinValue(),
							(long) getSelectedMaxValue());
				}
			}
			break;
		case MotionEvent.ACTION_UP:
			if (mIsDragging) {
				trackTouchEvent(event);
				onStopTrackingTouch();
				setPressed(false);
			} else {
				// Touch up when we never crossed the touch slop threshold
				// should be interpreted as a tap-seek to that location.
				onStartTrackingTouch();
				trackTouchEvent(event);
				onStopTrackingTouch();
			}

			pressedThumb = null;
			invalidate();
			if (listener != null) {
				listener.onRangeSeekBarValuesChanged(this,
						(long) getSelectedMinValue(),
						(long) getSelectedMaxValue());
			}
			break;
		case MotionEvent.ACTION_POINTER_DOWN: {
			final int index = event.getPointerCount() - 1;
			// final int index = ev.getActionIndex();
			mDownMotionX = event.getX(index);
			mActivePointerId = event.getPointerId(index);
			invalidate();
			break;
		}
		case MotionEvent.ACTION_POINTER_UP:
			onSecondaryPointerUp(event);
			invalidate();
			break;
		case MotionEvent.ACTION_CANCEL:
			if (mIsDragging) {
				onStopTrackingTouch();
				setPressed(false);
			}
			invalidate(); // see above explanation
			break;
		}
		return true;
	}

	private final void onSecondaryPointerUp(MotionEvent ev) {
		final int pointerIndex = (ev.getAction() & ACTION_POINTER_INDEX_MASK) >> ACTION_POINTER_INDEX_SHIFT;

		final int pointerId = ev.getPointerId(pointerIndex);
		if (pointerId == mActivePointerId) {
			// This was our active pointer going up. Choose
			// a new active pointer and adjust accordingly.
			// TODO: Make this decision more intelligent.
			final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
			mDownMotionX = ev.getX(newPointerIndex);
			mActivePointerId = ev.getPointerId(newPointerIndex);
		}
	}

	private final void trackTouchEvent(MotionEvent event) {
		final int pointerIndex = event.findPointerIndex(mActivePointerId);
		final float x = event.getX(pointerIndex);

		if (Thumb.MIN.equals(pressedThumb)) {
			setNormalizedMinValue(screenToNormalized(x));
		} else if (Thumb.MAX.equals(pressedThumb)) {
			setNormalizedMaxValue(screenToNormalized(x));
		}
	}

	/**
	 * Tries to claim the user's drag motion, and requests disallowing any
	 * ancestors from stealing events in the drag.
	 */
	private void attemptClaimDrag() {
		if (getParent() != null) {
			getParent().requestDisallowInterceptTouchEvent(true);
		}
	}

	/**
	 * This is called when the user has started touching this widget.
	 */
	void onStartTrackingTouch() {
		mIsDragging = true;
	}

	/**
	 * This is called when the user either releases his touch or the touch is
	 * canceled.
	 */
	void onStopTrackingTouch() {
		mIsDragging = false;
	}

	/**
	 * Ensures correct size of the widget.
	 */
	@Override
	protected synchronized void onMeasure(int widthMeasureSpec,
			int heightMeasureSpec) {
		int width = 200;
		if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(widthMeasureSpec)) {
			width = MeasureSpec.getSize(widthMeasureSpec);
		}
		int height = thumbRightImage.getHeight();
		if (MeasureSpec.UNSPECIFIED != MeasureSpec.getMode(heightMeasureSpec)) {
			height = Math.min(height, MeasureSpec.getSize(heightMeasureSpec));
		}
		setMeasuredDimension(width, height);
	}

	/**
	 * Draws the widget on the given canvas.
	 */
	@Override
	protected synchronized void onDraw(Canvas canvas) {
		super.onDraw(canvas);

		paint.setStyle(Style.FILL);
		paint.setAntiAlias(true);

		if (!IS_MULTI_COLORED) {
			// draw seek bar background line
			final RectF rect = new RectF(padding,
					0.5f * (getHeight() - thumbWidth -4), getWidth() - padding,
					0.5f * (getHeight() + thumbWidth + 4));
			paint.setColor(BACKGROUND_COLOR);
			canvas.drawRect(rect, paint);

			// draw seek bar active range line
			rect.left = normalizedToScreen(normalizedMinValue);
			rect.right = normalizedToScreen(normalizedMaxValue);

			// orange color
			paint.setColor(SINGLE_COLOR);
			canvas.drawRect(rect, paint);
		} else {
			final RectF rectR = new RectF(padding,
					0.5f * (getHeight() - lineHeight),
					normalizedToScreen(normalizedMinValue),
					0.5f * (getHeight() + lineHeight));
			paint.setColor(LEFT_COLOR);
			canvas.drawRect(rectR, paint);

			// draw seek bar background line
			final RectF rectY = new RectF(padding,
					0.5f * (getHeight() - lineHeight), getWidth() - padding,
					0.5f * (getHeight() + lineHeight));

			// draw seek bar active range line
			rectY.left = normalizedToScreen(normalizedMinValue);
			rectY.right = normalizedToScreen(normalizedMaxValue);

			paint.setColor(MIDDLE_COLOR);
			canvas.drawRect(rectY, paint);

			final RectF rectG = new RectF(
					normalizedToScreen(normalizedMaxValue),
					0.5f * (getHeight()/2 - lineHeight), getWidth() - padding,
					0.5f * (getHeight()/2 + lineHeight));
			paint.setColor(RIGHT_COLOR);
			canvas.drawRect(rectG, paint);
		}

		// draw minimum thumb
		drawThumb(normalizedToScreen(normalizedMinValue), thumbLeftImage,
				canvas);

		// draw maximum thumb
		drawThumb(normalizedToScreen(normalizedMaxValue), thumbRightImage,
				canvas);

		drawProgressThumb(canvas);

	}

	public void setProgress(double progress) {
		if (this.progress != progress) {
			this.progress = progress;
			postInvalidate();
		}
	}

	private void drawProgressThumb(Canvas canvas) {
		double normalizedProgress = valueToNormalized(progress);
		normalizedProgress = Math.min(normalizedMaxValue,
				Math.max(normalizedProgress, normalizedMinValue));
		canvas.drawBitmap(progressImage, normalizedToScreen(normalizedProgress)
				- thumbHalfWidth,
				(float) ((0.5f * getHeight()) - thumbHalfHeight/2 - 2), paint);

	}

	/**
	 * Overridden to save instance state when device orientation changes. This
	 * method is called automatically if you assign an id to the RangeSeekBar
	 * widget using the {@link #setId(int)} method. Other members of this class
	 * than the normalized min and max values don't need to be saved.
	 */
	@Override
	protected Parcelable onSaveInstanceState() {
		final Bundle bundle = new Bundle();
		bundle.putParcelable("SUPER", super.onSaveInstanceState());
		bundle.putDouble("MIN", normalizedMinValue);
		bundle.putDouble("MAX", normalizedMaxValue);
		return bundle;
	}

	/**
	 * Overridden to restore instance state when device orientation changes.
	 * This method is called automatically if you assign an id to the
	 * RangeSeekBar widget using the {@link #setId(int)} method.
	 */
	@Override
	protected void onRestoreInstanceState(Parcelable parcel) {
		final Bundle bundle = (Bundle) parcel;
		super.onRestoreInstanceState(bundle.getParcelable("SUPER"));
		normalizedMinValue = bundle.getDouble("MIN");
		normalizedMaxValue = bundle.getDouble("MAX");
	}

	/**
	 * Draws the "normal" resp. "pressed" thumb image on specified x-coordinate.
	 * 
	 * @param screenCoord
	 *            The x-coordinate in screen space where to draw the image.
	 * @param pressed
	 *            Is the thumb currently in "pressed" state?
	 * @param canvas
	 *            The canvas to draw upon.
	 */
	private void drawThumb(float screenCoord, Bitmap bm, Canvas canvas) {
		canvas.drawBitmap(bm, screenCoord - thumbHalfWidth,
				(float) ((0.5f * getHeight()) - thumbHalfHeight), paint);
	}

	/**
	 * Decides which (if any) thumb is touched by the given x-coordinate.
	 * 
	 * @param touchX
	 *            The x-coordinate of a touch event in screen space.
	 * @return The pressed thumb or null if none has been touched.
	 */
	private Thumb evalPressedThumb(float touchX) {
		Thumb result = null;
		boolean minThumbPressed = isInThumbRange(touchX, normalizedMinValue);
		boolean maxThumbPressed = isInThumbRange(touchX, normalizedMaxValue);
		if (minThumbPressed && maxThumbPressed) {
			// if both thumbs are pressed (they lie on top of each other),
			// choose the one with more room to drag. this avoids "stalling" the
			// thumbs in a corner, not being able to drag them apart anymore.
			result = (touchX / getWidth() > 0.5f) ? Thumb.MIN : Thumb.MAX;
		} else if (minThumbPressed) {
			result = Thumb.MIN;
		} else if (maxThumbPressed) {
			result = Thumb.MAX;
		}
		return result;
	}

	/**
	 * Decides if given x-coordinate in screen space needs to be interpreted as
	 * "within" the normalized thumb x-coordinate.
	 * 
	 * @param touchX
	 *            The x-coordinate in screen space to check.
	 * @param normalizedThumbValue
	 *            The normalized x-coordinate of the thumb to check.
	 * @return true if x-coordinate is in thumb range, false otherwise.
	 */
	private boolean isInThumbRange(float touchX, double normalizedThumbValue) {
		return Math.abs(touchX - normalizedToScreen(normalizedThumbValue)) <= thumbHalfWidth;
	}

	/**
	 * Sets normalized min value to value so that 0 <= value <= normalized max
	 * value <= 1. The View will get invalidated when calling this method.
	 * 
	 * @param value
	 *            The new normalized min value to set.
	 */
	public void setNormalizedMinValue(double value) {
		normalizedMinValue = Math.max(0d,
				Math.min(1d, Math.min(value, normalizedMaxValue)));
		invalidate();
	}

	/**
	 * Sets normalized max value to value so that 0 <= normalized min value <=
	 * value <= 1. The View will get invalidated when calling this method.
	 * 
	 * @param value
	 *            The new normalized max value to set.
	 */
	public void setNormalizedMaxValue(double value) {
		normalizedMaxValue = Math.max(0d,
				Math.min(1d, Math.max(value, normalizedMinValue)));
		invalidate();
	}

	public void setMax(double value) {
		absoluteMaxValuePrim = value;
	}

	public void reset() {
		normalizedMaxValue = 1;
		normalizedMinValue = 0;
		invalidate();
	}

	public boolean hasChanged() {
		return absoluteMinValuePrim != getSelectedMinValue()
				|| absoluteMaxValuePrim != getSelectedMaxValue();
	}

	/**
	 * Converts a normalized value to a Number object in the value space between
	 * absolute minimum and maximum.
	 * 
	 * @param normalized
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private double normalizedToValue(double normalized) {
		return absoluteMinValuePrim + normalized
				* (absoluteMaxValuePrim - absoluteMinValuePrim);
	}

	/**
	 * Converts the given Number value to a normalized double.
	 * 
	 * @param value
	 *            The Number value to normalize.
	 * @return The normalized double.
	 */
	private double valueToNormalized(double value) {
		if (0 == absoluteMaxValuePrim - absoluteMinValuePrim) {
			// prevent division by zero, simply return 0.
			return 0d;
		}
		return (value - absoluteMinValuePrim)
				/ (absoluteMaxValuePrim - absoluteMinValuePrim);
	}

	/**
	 * Converts a normalized value into screen space.
	 * 
	 * @param normalizedCoord
	 *            The normalized value to convert.
	 * @return The converted value in screen space.
	 */
	private float normalizedToScreen(double normalizedCoord) {
		return (float) (padding + normalizedCoord * (getWidth() - 2 * padding));
	}

	/**
	 * Converts screen space x-coordinates into normalized values.
	 * 
	 * @param screenCoord
	 *            The x-coordinate in screen space to convert.
	 * @return The normalized value.
	 */
	private double screenToNormalized(float screenCoord) {
		int width = getWidth();
		if (width <= 2 * padding) {
			// prevent division by zero, simply return 0.
			return 0d;
		} else {
			double result = (screenCoord - padding) / (width - 2 * padding);
			return Math.min(1d, Math.max(0d, result));
		}
	}

	/**
	 * Thumb constants (min and max).
	 */
	private static enum Thumb {
		MIN, MAX
	};

	/**
	 * Utility enumaration used to convert between Numbers and doubles.
	 * 
	 * @author Stephan Tittel (stephan.tittel@kom.tu-darmstadt.de)
	 * 
	 */
	private static enum NumberType {
		LONG, DOUBLE, INTEGER, FLOAT, SHORT, BYTE, BIG_DECIMAL;

		public static <E extends Number> NumberType fromNumber(E value)
				throws IllegalArgumentException {
			if (value instanceof Long) {
				return LONG;
			}
			if (value instanceof Double) {
				return DOUBLE;
			}
			if (value instanceof Integer) {
				return INTEGER;
			}
			if (value instanceof Float) {
				return FLOAT;
			}
			if (value instanceof Short) {
				return SHORT;
			}
			if (value instanceof Byte) {
				return BYTE;
			}
			if (value instanceof BigDecimal) {
				return BIG_DECIMAL;
			}
			throw new IllegalArgumentException("Number class '"
					+ value.getClass().getName() + "' is not supported");
		}

		public Number toNumber(double value) {
			switch (this) {
			case LONG:
				return new Long((long) value);
			case DOUBLE:
				return value;
			case INTEGER:
				return new Integer((int) value);
			case FLOAT:
				return new Float(value);
			case SHORT:
				return new Short((short) value);
			case BYTE:
				return new Byte((byte) value);
			case BIG_DECIMAL:
				return new BigDecimal(value);
			}
			throw new InstantiationError("can't convert " + this
					+ " to a Number object");
		}
	}
}

在活动中的调用代码如下:

//裁剪对话框
	private void showTrimDialog() {
		musicPlayer = new MusicPlayer(getMainLooper());
		final FileInfo lFileInfo = mFileViewInteractionHub.getItem(mMeunItem);
		final MusicTrimView view = new MusicTrimView(this);
		final MarkerDialog dialog = new MarkerDialog(this);
		dialog.show();
		//把自定义View添加到Dialog中
		dialog.setMarkerView(view);
		//设置Dialog上的歌曲名字
		dialog.setMarkerTitle(lFileInfo.fileName);
		dialog.setOnDismissListener(this);
		//设置Dialog上的时间
		dialog.setMarkerTitleDes(TimeUtils.formatAsMS(lFileInfo.totaltime));
		//设置Dialog的图标
		dialog.setMarkerIcon(R.drawable.item_pause);
		dialog.setMarkerConfirmDrawable(R.drawable.dialog_button_confirm_selector);
		//播放暂停
		dialog.setIconClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				if (musicPlayer.isPlaying()) {
					dialog.setMarkerIcon(R.drawable.item_play);
					musicPlayer.pause();
				} else {
					dialog.setMarkerIcon(R.drawable.item_pause);
					musicPlayer.start();
				}
			}
		});
		//确定裁剪
		dialog.setConfirmListener(null, new MarkerDialog.OnClickListener() {
			@Override
			public boolean onClick(View v) {
				//裁剪功能的实现
				String outPath = new File(lFileInfo.filePath).getParent() + "/"
						+ System.currentTimeMillis() + ".amr";
				boolean isSuccessed = AudioManager.trimMusic(lFileInfo.filePath,
						outPath, view.getMinTime(),view.getMaxTime());
				Toast.makeText(getContext(), isSuccessed ? "成功-" + outPath : "失败", 1)
						.show();
				
				return true;
			}
		});
		//设置时长
		view.setDuration(lFileInfo.totaltime);
		view.setonTimeChangedListener(new onTimeChangedListener() {

			@Override
			public void onMinTimeChanged(long minTime) {//获取最小值
				musicPlayer.setMinValue((int) minTime);
			}

			@Override
			public void onMaxTimeChanged(long maxTime) {//获取最大值
				musicPlayer.setMaxValue((int) maxTime);
			}
		});
		//设置最大值
		musicPlayer.setMaxValue((int) lFileInfo.totaltime);
		//设置播放音乐的路径
		musicPlayer.setSource(lFileInfo.filePath);
		musicPlayer.start();
		//调用接口传递进度给自定义View中seekBar
		musicPlayer.setonMusicProgressChangedListener(view);
	}


下面就是裁剪功能的实现,工具类:

public class AudioManager {
	
	public static final String[] EXTENSION = {".mp3",".aac",".amr",".wav"};
    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;
    public static final Object[] lock = new Object[0];
    
    /**
     * 音乐截取
     * @param inPath
     * @param outPath
     * @param start
     * @param end
     */
	public static boolean trimMusic(String inPath, String outPath, double start, double end) {
		try {
			long time = System.currentTimeMillis();
			File mFile = new File(inPath);
	//        String mExtension = getExtensionFromFilename(inPath);
	        CheapSoundFile  mSoundFile = CheapSoundFile.create(mFile.getAbsolutePath(), null);
	        int startFrame = secondsToFrames(start/1000, mSoundFile.getSampleRate(), mSoundFile.getSamplesPerFrame());
	        int endFrame = secondsToFrames(end/1000, mSoundFile.getSampleRate(), mSoundFile.getSamplesPerFrame());
			mSoundFile.WriteFile(createOutFile(outPath), startFrame, endFrame-startFrame);
			return true;
		} catch (IOException e) {
			e.printStackTrace();
		}
		return false;
    }
	
	public static File createOutFile(String outPath) {
		synchronized (lock) {
			File outFile = new File(outPath);
			if (!outFile.getParentFile().exists()) {
				outFile.getParentFile().mkdirs();
			}
			if (outFile.exists()) {
				outFile.delete();
			}
			return outFile;
		}
	}
	
	/**
	 * 根据时间计算帧
	 * @param seconds
	 * @return
	 */
	 public static int secondsToFrames(double seconds, double mSampleRate, double mSamplesPerFrame) {
	        return (int)(1.0 * seconds * mSampleRate / mSamplesPerFrame + 0.5);
	    }
	
	 /**
     * 获取后缀名, 包括点
     */
	public static String getExtensionFromFilename(String path) {
        return path.substring(path.lastIndexOf('.'));
    }
	
	/**
	 * 判断格式是否支持
	 * @param mFileName
	 * @return
	 */
	public static boolean isSupportable(String mFileName) {
		for (String mExten : EXTENSION) {
			if (mFileName.endsWith(mExten)) {
				return true;
			}
		}
		return false;
	}
}

判断格式的工具类代码如下:

public class CheapSoundFile {
    public interface ProgressListener {
        /**
         * Will be called by the CheapSoundFile subclass periodically
         * with values between 0.0 and 1.0.  Return true to continue
         * loading the file, and false to cancel.
         */
        boolean reportProgress(double fractionComplete);
    }

    public interface Factory {
        public CheapSoundFile create();
        public String[] getSupportedExtensions();
    }

    static Factory[] sSubclassFactories = new Factory[] {
        CheapAAC.getFactory(),
        CheapAMR.getFactory(),
        CheapMP3.getFactory(),
        CheapWAV.getFactory(),
    };

    static ArrayList<String> sSupportedExtensions = new ArrayList<String>();
    static HashMap<String, Factory> sExtensionMap =
        new HashMap<String, Factory>();

    static {
        for (Factory f : sSubclassFactories) {
            for (String extension : f.getSupportedExtensions()) {
                sSupportedExtensions.add(extension);
                sExtensionMap.put(extension, f);
            }
        }
    }

	/**
	 * Static method to create the appropriate CheapSoundFile subclass
	 * given a filename.
	 *
	 * TODO: make this more modular rather than hardcoding the logic
	 */
    public static CheapSoundFile create(String fileName,
                                        ProgressListener progressListener)
        throws java.io.FileNotFoundException,
               java.io.IOException {
        File f = new File(fileName);
        if (!f.exists()) {
            throw new java.io.FileNotFoundException(fileName);
        }
        String name = f.getName().toLowerCase();
        String[] components = name.split("\\.");
        if (components.length < 2) {
            return null;
        }
        Factory factory = sExtensionMap.get(components[components.length - 1]);
        if (factory == null) {
            return null;
        }
        CheapSoundFile soundFile = factory.create();
        soundFile.setProgressListener(progressListener);
        soundFile.ReadFile(f);
        return soundFile;
    }

    public static boolean isFilenameSupported(String filename) {
        String[] components = filename.toLowerCase().split("\\.");
        if (components.length < 2) {
            return false;
        }
        return sExtensionMap.containsKey(components[components.length - 1]);
    }

	/**
	 * Return the filename extensions that are recognized by one of
	 * our subclasses.
	 */
    public static String[] getSupportedExtensions() {
        return sSupportedExtensions.toArray(
            new String[sSupportedExtensions.size()]);
    }

    protected ProgressListener mProgressListener = null;
    protected File mInputFile = null;

    protected CheapSoundFile() {
    }

    public void ReadFile(File inputFile)
        throws java.io.FileNotFoundException,
               java.io.IOException {
        mInputFile = inputFile;
    }

    public void setProgressListener(ProgressListener progressListener) {
        mProgressListener = progressListener;
    }

    public int getNumFrames() {
        return 0;
    }

    public int getSamplesPerFrame() {
        return 0;
    }

    public int[] getFrameOffsets() {
        return null;
    }

    public int[] getFrameLens() {
        return null;
    }

    public int[] getFrameGains() {
        return null;
    }

    public int getFileSizeBytes() {
        return 0;
    }

    public int getAvgBitrateKbps() {
        return 0;
    }

    public int getSampleRate() {
        return 0;
    }

    public int getChannels() {
        return 0;
    }

    public String getFiletype() {
        return "Unknown";
    }

    /**
     * If and only if this particular file format supports seeking
     * directly into the middle of the file without reading the rest of
     * the header, this returns the byte offset of the given frame,
     * otherwise returns -1.
     */
    public int getSeekableFrameOffset(int frame) {
        return -1;
    }

    private static final char[] HEX_CHARS = {
        '0', '1', '2', '3', '4', '5', '6', '7',
        '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
    public static String bytesToHex (byte hash[]) {
        char buf[] = new char[hash.length * 2];
        for (int i = 0, x = 0; i < hash.length; i++) {
            buf[x++] = HEX_CHARS[(hash[i] >>> 4) & 0xf];
            buf[x++] = HEX_CHARS[hash[i] & 0xf];
        }
        return new String(buf);
    }

    public String computeMd5OfFirst10Frames()
            throws java.io.FileNotFoundException,
                   java.io.IOException,
                   java.security.NoSuchAlgorithmException {
        int[] frameOffsets = getFrameOffsets();
        int[] frameLens = getFrameLens();
        int numFrames = frameLens.length;
        if (numFrames > 10) {
            numFrames = 10;
        }

        MessageDigest digest = java.security.MessageDigest.getInstance("MD5");
        FileInputStream in = new FileInputStream(mInputFile);
        int pos = 0;
        for (int i = 0; i < numFrames; i++) {
            int skip = frameOffsets[i] - pos;
            int len = frameLens[i];
            if (skip > 0) {
                in.skip(skip);
                pos += skip;
            }
            byte[] buffer = new byte[len];
            in.read(buffer, 0, len);
            digest.update(buffer);
            pos += len;
        }
        in.close();
        byte[] hash = digest.digest();
        return bytesToHex(hash);
    }

    public void WriteFile(File outputFile, int startFrame, int numFrames)
            throws java.io.IOException {
    }
};


AAC格式代码如下:

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 HashMap<Integer, Atom> mAtomMap;

    // 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<Integer, Atom>();

        // 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());
    } **/
};


AMR格式代码如下:

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());
    } **/
};


MP3格式代码如下:

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 && startFrame + i <mFrameLens.length; 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 };
};

WAV格式的代码如下:

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();
    }
};









  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值