首先为了方便音乐的截取,先把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();
}
};