Android实现录音声波图
作者:aikongmeng 时间:2022-08-05 20:55:49
本文实例为大家分享了Android实现录音声波图的具体代码,供大家参考,具体内容如下
图像类:
package com.akm.test;
/**
?* Created by toge on 15/12/9.
?*/
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.SurfaceView;
import java.util.LinkedList;
/**
?* A view that displays audio data on the screen as a waveform.
?*/
public class WaveformView extends SurfaceView {
? ? // The number of buffer frames to keep around (for a nice fade-out visualization).
? ? private static final int HISTORY_SIZE = 6;
? ? // To make quieter sounds still show up well on the display, we use +/- 8192 as the amplitude
? ? // that reaches the top/bottom of the view instead of +/- 32767. Any samples that have
? ? // magnitude higher than this limit will simply be clipped during drawing.
? ? private static final float MAX_AMPLITUDE_TO_DRAW = 8192.0f;
? ? // The queue that will hold historical audio data.
? ? private final LinkedList<short[]> mAudioData;
? ? private final Paint mPaint;
? ? public WaveformView(Context context) {
? ? ? ? this(context, null, 0);
? ? }
? ? public WaveformView(Context context, AttributeSet attrs) {
? ? ? ? this(context, attrs, 0);
? ? }
? ? public WaveformView(Context context, AttributeSet attrs, int defStyle) {
? ? ? ? super(context, attrs, defStyle);
? ? ? ? mAudioData = new LinkedList<short[]>();
? ? ? ? mPaint = new Paint();
? ? ? ? mPaint.setStyle(Paint.Style.STROKE);
? ? ? ? mPaint.setColor(Color.WHITE);
? ? ? ? mPaint.setStrokeWidth(0);
? ? ? ? mPaint.setAntiAlias(true);
? ? }
? ? /**
? ? ?* Updates the waveform view with a new "frame" of samples and renders it. The new frame gets
? ? ?* added to the front of the rendering queue, pushing the previous frames back, causing them to
? ? ?* be faded out visually.
? ? ?*
? ? ?* @param buffer the most recent buffer of audio samples
? ? ?*/
? ? public synchronized void updateAudioData(short[] buffer) {
? ? ? ? short[] newBuffer;
? ? ? ? // We want to keep a small amount of history in the view to provide a nice fading effect.
? ? ? ? // We use a linked list that we treat as a queue for this.
? ? ? ? if (mAudioData.size() == HISTORY_SIZE) {
? ? ? ? ? ? newBuffer = mAudioData.removeFirst();
? ? ? ? ? ? System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
? ? ? ? } else {
? ? ? ? ? ? newBuffer = buffer.clone();
? ? ? ? }
? ? ? ? mAudioData.addLast(newBuffer);
? ? ? ? // Update the display.
? ? ? ? Canvas canvas = getHolder().lockCanvas();
? ? ? ? if (canvas != null) {
? ? ? ? ? ? drawWaveform(canvas);
? ? ? ? ? ? getHolder().unlockCanvasAndPost(canvas);
? ? ? ? }
? ? }
? ? /**
? ? ?* Repaints the view's surface.
? ? ?*
? ? ?* @param canvas the {@link Canvas} object on which to draw
? ? ?*/
? ? private void drawWaveform(Canvas canvas) {
? ? ? ? // Clear the screen each time because SurfaceView won't do this for us.
? ? ? ? canvas.drawColor(Color.BLACK);
? ? ? ? float width = getWidth();
? ? ? ? float height = getHeight();
? ? ? ? float centerY = height / 2;
? ? ? ? // We draw the history from oldest to newest so that the older audio data is further back
? ? ? ? // and darker than the most recent data.
? ? ? ? int colorDelta = 255 / (HISTORY_SIZE + 1);
? ? ? ? int brightness = colorDelta;
? ? ? ? for (short[] buffer : mAudioData) {
? ? ? ? ? ? mPaint.setColor(Color.argb(brightness, 128, 255, 192));
? ? ? ? ? ? float lastX = -1;
? ? ? ? ? ? float lastY = -1;
? ? ? ? ? ? // For efficiency, we don't draw all of the samples in the buffer, but only the ones
? ? ? ? ? ? // that align with pixel boundaries.
? ? ? ? ? ? for (int x = 0; x < width; x++) {
? ? ? ? ? ? ? ? int index = (int) ((x / width) * buffer.length);
? ? ? ? ? ? ? ? short sample = buffer[index];
? ? ? ? ? ? ? ? float y = (sample / MAX_AMPLITUDE_TO_DRAW) * centerY + centerY;
? ? ? ? ? ? ? ? if (lastX != -1) {
? ? ? ? ? ? ? ? ? ? canvas.drawLine(lastX, lastY, x, y, mPaint);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? lastX = x;
? ? ? ? ? ? ? ? lastY = y;
? ? ? ? ? ? }
? ? ? ? ? ? brightness += colorDelta;
? ? ? ? }
? ? }
}
头文件:
/*
?* Copyright (C) 2015 Google Inc.
?*
?* Licensed under the Apache License, Version 2.0 (the "License");
?* you may not use this file except in compliance with the License.
?* You may obtain a copy of the License at
?*
?* ? ? ?http://www.apache.org/licenses/LICENSE-2.0
?*
?* Unless required by applicable law or agreed to in writing, software
?* distributed under the License is distributed on an "AS IS" BASIS,
?* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
?* See the License for the specific language governing permissions and
?* limitations under the License.
?*/
package com.akm.test;
import java.io.FileOutputStream;
import java.io.IOException;
/**
?* Created by Akm at 15/12/1 上午10:07
?* 文件制作
?*/
public class WAVHeader {
? ? private byte[] mHeader; ? ? ? ? ?// the complete header.
? ? private int mSampleRate; ? ? ? ? // sampling frequency in Hz (e.g. 44100).
? ? private int mChannels; ? ? ? ? ? // number of channels.
? ? private int mNumSamples; ? ? ? ? // total number of samples per channel.
? ? private int mNumBytesPerSample; ?// number of bytes per sample, all channels included.
? ? public WAVHeader(int sampleRate, int numChannels, int numSamples) {
? ? ? ? mSampleRate = sampleRate;
? ? ? ? mChannels = numChannels;
? ? ? ? mNumSamples = numSamples;
? ? ? ? mNumBytesPerSample = 2 * mChannels; ?// assuming 2 bytes per sample (for 1 channel)
? ? ? ? mHeader = null;
? ? ? ? setHeader();
? ? }
? ? public byte[] getWAVHeader() {
? ? ? ? return mHeader;
? ? }
? ? public static byte[] getWAVHeader(int sampleRate, int numChannels, int numSamples) {
? ? ? ? return new WAVHeader(sampleRate, numChannels, numSamples).mHeader;
? ? }
? ? public String toString() {
? ? ? ? String str = "";
? ? ? ? if (mHeader == null) {
? ? ? ? ? ? return str;
? ? ? ? }
? ? ? ? int num_32bits_per_lines = 8;
? ? ? ? int count = 0;
? ? ? ? for (byte b : mHeader) {
? ? ? ? ? ? boolean break_line = count > 0 && count % (num_32bits_per_lines * 4) == 0;
? ? ? ? ? ? boolean insert_space = count > 0 && count % 4 == 0 && !break_line;
? ? ? ? ? ? if (break_line) {
? ? ? ? ? ? ? ? str += '\n';
? ? ? ? ? ? }
? ? ? ? ? ? if (insert_space) {
? ? ? ? ? ? ? ? str += ' ';
? ? ? ? ? ? }
? ? ? ? ? ? str += String.format("%02X", b);
? ? ? ? ? ? count++;
? ? ? ? }
? ? ? ? return str;
? ? }
? ? private void setHeader() {
? ? ? ? byte[] header = new byte[46];
? ? ? ? int offset = 0;
? ? ? ? int size;
? ? ? ? // set the RIFF chunk
? ? ? ? System.arraycopy(new byte[]{'R', 'I', 'F', 'F'}, 0, header, offset, 4);
? ? ? ? offset += 4;
? ? ? ? size = 36 + mNumSamples * mNumBytesPerSample;
? ? ? ? header[offset++] = (byte) (size & 0xFF);
? ? ? ? header[offset++] = (byte) ((size >> 8) & 0xFF);
? ? ? ? header[offset++] = (byte) ((size >> 16) & 0xFF);
? ? ? ? header[offset++] = (byte) ((size >> 24) & 0xFF);
? ? ? ? System.arraycopy(new byte[]{'W', 'A', 'V', 'E'}, 0, header, offset, 4);
? ? ? ? offset += 4;
? ? ? ? // set the fmt chunk
? ? ? ? System.arraycopy(new byte[]{'f', 'm', 't', ' '}, 0, header, offset, 4);
? ? ? ? offset += 4;
? ? ? ? System.arraycopy(new byte[]{0x10, 0, 0, 0}, 0, header, offset, 4); ?// chunk size = 16
? ? ? ? offset += 4;
? ? ? ? System.arraycopy(new byte[]{1, 0}, 0, header, offset, 2); ?// format = 1 for PCM
? ? ? ? offset += 2;
? ? ? ? header[offset++] = (byte) (mChannels & 0xFF);
? ? ? ? header[offset++] = (byte) ((mChannels >> 8) & 0xFF);
? ? ? ? header[offset++] = (byte) (mSampleRate & 0xFF);
? ? ? ? header[offset++] = (byte) ((mSampleRate >> 8) & 0xFF);
? ? ? ? header[offset++] = (byte) ((mSampleRate >> 16) & 0xFF);
? ? ? ? header[offset++] = (byte) ((mSampleRate >> 24) & 0xFF);
? ? ? ? int byteRate = mSampleRate * mNumBytesPerSample;
? ? ? ? header[offset++] = (byte) (byteRate & 0xFF);
? ? ? ? header[offset++] = (byte) ((byteRate >> 8) & 0xFF);
? ? ? ? header[offset++] = (byte) ((byteRate >> 16) & 0xFF);
? ? ? ? header[offset++] = (byte) ((byteRate >> 24) & 0xFF);
? ? ? ? header[offset++] = (byte) (mNumBytesPerSample & 0xFF);
? ? ? ? header[offset++] = (byte) ((mNumBytesPerSample >> 8) & 0xFF);
? ? ? ? System.arraycopy(new byte[]{0x10, 0}, 0, header, offset, 2);
? ? ? ? offset += 2;
? ? ? ? // set the beginning of the data chunk
? ? ? ? System.arraycopy(new byte[]{'d', 'a', 't', 'a'}, 0, header, offset, 4);
? ? ? ? offset += 4;
? ? ? ? size = mNumSamples * mNumBytesPerSample;
? ? ? ? header[offset++] = (byte) (size & 0xFF);
? ? ? ? header[offset++] = (byte) ((size >> 8) & 0xFF);
? ? ? ? header[offset++] = (byte) ((size >> 16) & 0xFF);
? ? ? ? header[offset++] = (byte) ((size >> 24) & 0xFF);
? ? ? ? mHeader = header;
? ? }
? ? public static byte[] getHeader( ?long totalAudioLen,
? ? ? ? ? ? ? ? ? ? ? ? ? ? long totalDataLen, long longSampleRate, int channels, long byteRate) throws IOException {
? ? ? ? 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) channels;
? ? ? ? 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 * 16 / 8); // 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);
? ? ? ? return header;
? ? }
}
测试:
package com.akm.test;
import android.app.Activity;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Environment;
import android.os.Message;
import android.os.SystemClock;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Chronometer;
import android.widget.SeekBar;
import android.widget.TextView;
import com.ringdroid.R;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
import java.nio.channels.FileChannel;
/**
?* Created by toge on 15/11/30.
?*/
public class Test extends Activity implements View.OnClickListener {
? ? WaveformView waveformView;
? ? private Button btnStart;
? ? private Button btnStop;
? ? private String filePath;
? ? private boolean mRecordingKeepGoing;
? ? private SoundFile soundFile;
? ? private RandomAccessFile randomAccessFile;
? ? private int totalLength;//总长
? ? private int duration;//时长
? ? private int rate;//采样率
? ? private int channelConfig;//声道
? ? private int samples;
? ? private int startPos;
? ? private int bufferSize;//缓冲区大小
? ? private int minBufferSize;//最小缓冲区
? ? private AudioRecord audioRecord;
? ? private AudioTrack audioTrack;
? ? private boolean mThreadFlag;
? ? private int i;
? ? private int j;
? ? private int STATUS = 1;
? ? private int STATUS_PAUSE = 2;
? ? private int STATUS_PREPARED = 1;
? ? private int STATUS_RECORDING = 1;
? ? private Thread audioTrackThread;
? ? private Thread thread;
? ? private int endPos;
? ? private int curFileLength;
? ? OnFileChangedListener onFileChangedListener;
? ? private boolean isRewrite;
? ? private boolean audioTrackFlag;
? ? private int frequency = 22050;//22050;
? ? private int recBufSize;
? ? private String outPath;
? ? private byte[] bytes;
? ? private int time;
? ? private Button btnPasue;
? ? private Button btnPlay;
? ? private Button btnPlay2;
? ? private long startTime;
? ? private long restOfTime;
? ? private int audioFormat;//采集
? ? private int bufferSizeInBytes;//缓冲区大小
? ? private Button btnSave;
? ? // ? ?private ByteBuffer mDecodedBytes;
// ? ?private ByteBuffer mDecodedSamples;
? ? private byte[] sampleBytes;
? ? private MediaPlayer mediaPlayer;
? ? private SeekBar seekBar;
? ? private android.os.Handler handler = new android.os.Handler();
? ? Runnable updateThread = new Runnable() {
? ? ? ? public void run() {
? ? ? ? ? ? // 获得歌曲现在播放位置并设置成播放进度条的值
? ? ? ? ? ? if (mediaPlayer != null) {
? ? ? ? ? ? ? ? seekBar.setProgress(mediaPlayer.getCurrentPosition());
? ? ? ? ? ? ? ? Log.e("Test", "run------ updateThread:getCurrentPosition " + mediaPlayer.getCurrentPosition());
? ? ? ? ? ? ? ? // 每次延迟100毫秒再启动线程
? ? ? ? ? ? ? ? handler.postDelayed(updateThread, 100);
? ? ? ? ? ? }
? ? ? ? }
? ? };
? ? private Chronometer et_time;
? ? private long falgTime;
? ? private long pauseTime;
? ? private long subtime;
? ? private long beginTime;
? ? private TextView currentTime;
? ? private int currentProgress;
? ? @Override
? ? protected void onCreate(Bundle savedInstanceState) {
? ? ? ? super.onCreate(savedInstanceState);
? ? ? ? setContentView(R.layout.test);
? ? ? ? waveformView = (WaveformView) findViewById(R.id.waveform);
? ? ? ? btnStart = (Button) findViewById(R.id.button_start);
? ? ? ? btnPasue = (Button) findViewById(R.id.button_pasue);
? ? ? ? btnStop = (Button) findViewById(R.id.button2_stop);
? ? ? ? btnPlay = (Button) findViewById(R.id.button_play);
? ? ? ? btnPlay2 = (Button) findViewById(R.id.button_play2);
? ? ? ? btnSave = (Button) findViewById(R.id.button_save);
? ? ? ? seekBar = (SeekBar) findViewById(R.id.sb);
? ? ? ? et_time = (Chronometer) this.findViewById(R.id.et_time);
? ? ? ? currentTime = (TextView) this.findViewById(R.id.currentTime);
? ? ? ? btnStart.setOnClickListener(this);
? ? ? ? btnPasue.setOnClickListener(this);
? ? ? ? btnStop.setOnClickListener(this);
? ? ? ? btnPlay.setOnClickListener(this);
? ? ? ? btnPlay2.setOnClickListener(this);
? ? ? ? btnSave.setOnClickListener(this);
? ? ? ? initPar();
? ? ? ? initRecorder(true);
? ? ? ? initAudioTack();
? ? }
? ? @Override
? ? protected void onResume() {
? ? ? ? super.onResume();
? ? ? ? initRecorder(false);
? ? }
? ? private void initAudioTack() {
? ? ? ? minBufferSize = AudioTrack.getMinBufferSize(rate, 3, audioFormat);
? ? ? ? Log.e("Test", "initAudioTack------ minBufferSize:" + minBufferSize);
? ? ? ? audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, rate, 1, audioFormat, minBufferSize, AudioTrack.MODE_STREAM);
? ? }
? ? private void initPar() {
? ? ? ? duration = 60 * 1000;//毫秒
? ? ? ? rate = 44100;//声卡一般提供11.025kHz、22.05kHz和44.1kHz等不同的采样频率
? ? ? ? channelConfig = AudioFormat.CHANNEL_IN_DEFAULT;
? ? ? ? audioFormat = AudioFormat.ENCODING_PCM_16BIT;
? ? ? ? restOfTime = duration;
? ? }
? ? private void initRecorder(boolean isNew) {
? ? ? ? initAudioFile(isNew);
? ? ? ? bufferSize = AudioRecord.getMinBufferSize(rate, channelConfig, audioFormat);
? ? ? ? Log.d("Test", "initRecorder: bufferSize:" + bufferSize);//44100 1 2 = v fc
// ? ? ? ?int k = audioFormat * rate / 25;
// ? ? ? ?if (bufferSize % k != 0) {
// ? ? ? ? ? ?bufferSize = (k * (channelConfig + bufferSize / k));
// ? ? ? ?}
? ? ? ? audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, rate, channelConfig, audioFormat, bufferSize);
? ? }
? ? public boolean isRecording() {
? ? ? ? return audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING;
? ? }
? ? private void initAudioFile(boolean isNew) {
? ? ? ? filePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/akm/t.wav";
? ? ? ? new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/akm").mkdir();
? ? ? ? File f = new File(filePath);
? ? ? ? try {
? ? ? ? ? ? if (f.exists() && isNew) {
? ? ? ? ? ? ? ? f.delete();
? ? ? ? ? ? }
? ? ? ? ? ? randomAccessFile = new RandomAccessFile(filePath, "rw");
? ? ? ? ? ? //文件长度 = 比特率*时间
? ? ? ? ? ? //= 采样率*位数*声道 / 8
? ? ? ? ? ? totalLength = (rate * 1 * 16 / 8) * (duration / 1000);
// ? ? ? ? ? ?totalLength = (int) (4l * (duration * rate / 1000l));
? ? ? ? ? ? createWaveFile(randomAccessFile, true, totalLength);
? ? ? ? ? ? totalLength = 44 + totalLength;
? ? ? ? ? ? randomAccessFile = new RandomAccessFile(filePath, "rw");
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
? ? private void createWaveFile(RandomAccessFile randomAccessFile, boolean b, int totalLength) {
? ? ? ? if (b) {
? ? ? ? ? ? try {
// ? ? ? ? ? ? ? ?randomAccessFile.write(WAVHeader.getHeader(rate,channels,samples));
? ? ? ? ? ? ? ? // long totalAudioLen,long totalDataLen, long longSampleRate, int channels, long byteRate
? ? ? ? ? ? ? ? //数据长度 文件长度 采样率 声道 比特率
? ? ? ? ? ? ? ? //比特率(字节/秒)= (采样频率(Hz)× 采样位数(bit) × 声道数)/ 8
? ? ? ? ? ? ? ? //
? ? ? ? ? ? ? ? long byteRate = (channelConfig * audioFormat * rate) / 8;
? ? ? ? ? ? ? ? byte[] bytes = WAVHeader.getHeader(totalLength - 36l, totalLength, rate, channelConfig, byteRate);
? ? ? ? ? ? ? ? FileChannel localFileChannel = randomAccessFile.getChannel();
? ? ? ? ? ? ? ? localFileChannel.map(FileChannel.MapMode.READ_WRITE, 0l, 44l).put(bytes);
? ? ? ? ? ? ? ? localFileChannel.close();
? ? ? ? ? ? ? ? Log.e("Test", "createWaveFile------ OK ");
? ? ? ? ? ? } catch (IOException e) {
? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? }
? ? ? ? }
? ? }
? ? @Override
? ? public void onClick(View view) {
? ? ? ? switch (view.getId()) {
? ? ? ? ? ? case R.id.button_start:
? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? mRecordingKeepGoing = true;
? ? ? ? ? ? ? ? ? ? new Thread() {
? ? ? ? ? ? ? ? ? ? ? ? @Override
? ? ? ? ? ? ? ? ? ? ? ? public void run() {
? ? ? ? ? ? ? ? ? ? ? ? ? ? startRecording();
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? }.start();
? ? ? ? ? ? ? ? } catch (Exception e) {
? ? ? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case R.id.button_pasue:
? ? ? ? ? ? ? ? mRecordingKeepGoing = false;
? ? ? ? ? ? ? ? pauseRecord();
? ? ? ? ? ? ? ? try {//暂停后,设置文件索引为末尾
? ? ? ? ? ? ? ? ? ? startPos = (int) randomAccessFile.getFilePointer();
? ? ? ? ? ? ? ? } catch (IOException e) {
? ? ? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? pausePlay();
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case R.id.button_play:
? ? ? ? ? ? ? ? startReview(true);
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case R.id.button2_stop:
? ? ? ? ? ? ? ? mRecordingKeepGoing = false;
? ? ? ? ? ? ? ? stopRecord();
? ? ? ? ? ? ? ? stopPlay();
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case R.id.button_save:
? ? ? ? ? ? ? ? startPos = currentProgress;
? ? ? ? ? ? ? ? if (randomAccessFile==null){
? ? ? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? ? ? randomAccessFile = new RandomAccessFile(filePath, "rw");
? ? ? ? ? ? ? ? ? ? } catch (IOException e) {
? ? ? ? ? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? Log.e("Test", "onClick: pos" + randomAccessFile.getFilePointer());
? ? ? ? ? ? ? ? ? ? int size = ((rate * 1 * 16 / 8) * (currentProgress / 1000));
? ? ? ? ? ? ? ? ? ? Log.e("Test", "onClick------ size "+size);
? ? ? ? ? ? ? ? ? ? if (size<44){
? ? ? ? ? ? ? ? ? ? ? ? size = 44;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? randomAccessFile.seek(size);
? ? ? ? ? ? ? ? ? ? randomAccessFile.write(sampleBytes);
? ? ? ? ? ? ? ? ? ? randomAccessFile.close();
? ? ? ? ? ? ? ? } catch (IOException e) {
? ? ? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? Log.e("Test", "onClick------pos "+currentProgress);
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case R.id.button_play2:
? ? ? ? ? ? ? ? play();
? ? ? ? ? ? ? ? break;
? ? ? ? }
? ? }
? ? private void pausePlay() {
? ? ? ? if (mediaPlayer != null && mediaPlayer.isPlaying()) {
? ? ? ? ? ? // 暂停音乐播放器
? ? ? ? ? ? mediaPlayer.pause();
? ? ? ? ? ? btnPasue.setText("续播");
? ? ? ? ? ? seekBar.setEnabled(false);
? ? ? ? ? ? et_time.stop();
? ? ? ? ? ? pauseTime = SystemClock.elapsedRealtime();
? ? ? ? ? ? // System.out.println("1 pauseTime" + pauseTime);
? ? ? ? } else if (mediaPlayer != null
? ? ? ? ? ? ? ? && "续播".equals(btnPasue.getText().toString())) {
? ? ? ? ? ? subtime += SystemClock.elapsedRealtime() - pauseTime;
? ? ? ? ? ? // System.out.println("2 subtime:" + subtime);
? ? ? ? ? ? mediaPlayer.start();
? ? ? ? ? ? btnPasue.setText("暂停");
? ? ? ? ? ? seekBar.setEnabled(true);
? ? ? ? ? ? beginTime = falgTime + subtime;
? ? ? ? ? ? // System.out.println("3 beginTime" + beginTime);
? ? ? ? ? ? et_time.setBase(beginTime);
? ? ? ? ? ? et_time.start();
? ? ? ? }
? ? }
? ? private void stopPlay() {
? ? ? ? if (mediaPlayer != null && mediaPlayer.isPlaying()) {
? ? ? ? ? ? mediaPlayer.stop();
? ? ? ? ? ? mediaPlayer = null;
? ? ? ? ? ? et_time.setBase(SystemClock.elapsedRealtime());
? ? ? ? ? ? et_time.start();
? ? ? ? ? ? et_time.stop();
? ? ? ? ? ? btnPlay2.setEnabled(true);
? ? ? ? ? ? btnPlay2.setClickable(true);
? ? ? ? }
? ? ? ? falgTime = 0;
? ? ? ? subtime = 0;
? ? ? ? seekBar.setProgress(0);
? ? ? ? seekBar.setEnabled(false);
? ? }
? ? private void play() {
? ? ? ? mediaPlayer = new MediaPlayer();
? ? ? ? try {
? ? ? ? ? ? mediaPlayer.setDataSource(filePath);
? ? ? ? ? ? mediaPlayer.prepareAsync();
? ? ? ? ? ? // 为播放器注册
? ? ? ? ? ? mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
? ? ? ? ? ? ? ? public void onPrepared(MediaPlayer mp) {
? ? ? ? ? ? ? ? ? ? // TODO Auto-generated method stub
? ? ? ? ? ? ? ? ? ? mediaPlayer.start();
? ? ? ? ? ? ? ? ? ? btnPlay2.setEnabled(false);
? ? ? ? ? ? ? ? ? ? btnPlay2.setClickable(false);
? ? ? ? ? ? ? ? ? ? seekBar.setMax(mediaPlayer.getDuration());
? ? ? ? ? ? ? ? ? ? handler.post(updateThread);
? ? ? ? ? ? ? ? ? ? seekBar.setEnabled(true);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? });
? ? ? ? ? ? // 注册播放完毕后的监听事件
? ? ? ? ? ? mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
? ? ? ? ? ? ? ? public void onCompletion(MediaPlayer mp) {
// ? ? ? ? ? ? ? ? ? ?mediaPlayer.release();
// ? ? ? ? ? ? ? ? ? ?mediaPlayer = null;
? ? ? ? ? ? ? ? ? ? btnPlay2.setEnabled(true);
? ? ? ? ? ? ? ? ? ? btnPlay2.setClickable(true);
? ? ? ? ? ? ? ? ? ? et_time.setBase(SystemClock.elapsedRealtime());
? ? ? ? ? ? ? ? ? ? et_time.start();
? ? ? ? ? ? ? ? ? ? et_time.stop();
? ? ? ? ? ? ? ? ? ? seekBar.setProgress(0);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? });
? ? ? ? ? ? falgTime = SystemClock.elapsedRealtime();
? ? ? ? ? ? et_time.setBase(falgTime);
? ? ? ? ? ? et_time.start();
? ? ? ? ? ? et_time.setOnChronometerTickListener(new Chronometer.OnChronometerTickListener() {
? ? ? ? ? ? ? ? @Override
? ? ? ? ? ? ? ? public void onChronometerTick(Chronometer chronometer) {
? ? ? ? ? ? ? ? }
? ? ? ? ? ? });
? ? ? ? ? ? seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
? ? ? ? ? ? ? ? @Override
? ? ? ? ? ? ? ? public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
? ? ? ? ? ? ? ? ? ? if (fromUser == true && mediaPlayer != null) {
? ? ? ? ? ? ? ? ? ? ? ? Log.e("Test", "onProgressChanged------ progress "+progress);
? ? ? ? ? ? ? ? ? ? ? ? mediaPlayer.seekTo(progress);
? ? ? ? ? ? ? ? ? ? ? ? falgTime = SystemClock.elapsedRealtime();
? ? ? ? ? ? ? ? ? ? ? ? beginTime = falgTime - seekBar.getProgress();
? ? ? ? ? ? ? ? ? ? ? ? et_time.setBase(beginTime);
// ? ? ? ? ? ? ? ? ? ? ? ?et_time.start();
// ? ? ? ? ? ? ? ? ? ? ? ?final int ctime = mediaPlayer.getDuration() / progress;
? ? ? ? ? ? ? ? ? ? ? ? //时间*比特率 = 大小 (rate * 1 * 16 / 8) * (duration / 1000);
? ? ? ? ? ? ? ? ? ? ? ? //时间 = 大小/比特率
? ? ? ? ? ? ? ? ? ? ? ? int ctime = progress/((rate * 1 * 16 / 8) * (duration / 1000));
? ? ? ? ? ? ? ? ? ? ? ? currentTime.setText( ctime+ "s");
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? @Override
? ? ? ? ? ? ? ? public void onStartTrackingTouch(SeekBar seekBar) {
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? @Override
? ? ? ? ? ? ? ? public void onStopTrackingTouch(SeekBar seekBar) {
// ? ? ? ? ? ? ? ? ? ?startPos = seekBar.getProgress();
? ? ? ? ? ? ? ? ? ? currentProgress = seekBar.getProgress();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? });
? ? ? ? } catch (IOException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? ? ? // mediaPlayer.prepare(); // c/c++ 播放器引擎的初始化
? ? ? ? // 同步方法
? ? ? ? // 采用异步的方式
? ? }
? ? private OnEventListener onEventListener;
? ? public void setOnEventListener(OnEventListener onEventListener) {
? ? ? ? this.onEventListener = onEventListener;
? ? }
? ? android.os.Handler errorHandler = new android.os.Handler() {
? ? ? ? @Override
? ? ? ? public void handleMessage(Message msg) {
? ? ? ? ? ? if (onEventListener != null) {
? ? ? ? ? ? ? ? onEventListener.onError("error");
? ? ? ? ? ? }
? ? ? ? }
? ? };
? ? private void startThread() {
? ? ? ? if (startPos == 0) {//开始位置
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? randomAccessFile.seek(44);
? ? ? ? ? ? } catch (IOException e) {
? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? mThreadFlag = true;
? ? ? ? i = 0;
? ? ? ? j = -1;
? ? ? ? STATUS = 1;
? ? ? ? thread = new Thread() {
? ? ? ? ? ? @Override
? ? ? ? ? ? public void run() {
? ? ? ? ? ? ? ? if (startTime == 0) {
? ? ? ? ? ? ? ? ? ? startTime = System.currentTimeMillis();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? byte[] arrayOfByte = new byte[bufferSize];
? ? ? ? ? ? ? ? short[] arrayOfShort = new short[bufferSize];
? ? ? ? ? ? ? ? while (mThreadFlag) {
? ? ? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? ? ? if (audioRecord == null || STATUS != 1) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? continue;
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? int bufferReadResult = audioRecord.read(arrayOfByte, 0, bufferSize);
? ? ? ? ? ? ? ? ? ? ? ? int bufferReadResult2 = audioRecord.read(arrayOfShort, 0, bufferSize);
? ? ? ? ? ? ? ? ? ? ? ? Log.e("Test", "run------ bufferReadResult "+bufferReadResult);
? ? ? ? ? ? ? ? ? ? ? ? Log.e("Test", "run------ bufferReadResult2 "+bufferReadResult2);
? ? ? ? ? ? ? ? ? ? ? ? if (audioRecord.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING || bufferReadResult <= 0) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? i = 1;
? ? ? ? ? ? ? ? ? ? ? ? randomAccessFile.write(arrayOfByte);
? ? ? ? ? ? ? ? ? ? ? ? waveformView.updateAudioData(arrayOfShort);
? ? ? ? ? ? ? ? ? ? ? ? byte[] tmpBuf = new byte[bufferReadResult];
? ? ? ? ? ? ? ? ? ? ? ? System.arraycopy(arrayOfByte, 0, tmpBuf, 0, bufferReadResult);
? ? ? ? ? ? ? ? ? ? ? ? audioTrack.write(tmpBuf, 0, tmpBuf.length);
? ? ? ? ? ? ? ? ? ? ? ? pjByteBuffer(tmpBuf);
? ? ? ? ? ? ? ? ? ? ? ? curFileLength = (int) randomAccessFile.length() / 1024;
? ? ? ? ? ? ? ? ? ? ? ? Log.e("Test", "run------ curFilelength:" + curFileLength + ",startPos:" + startPos + ",endPos:" + endPos + ",isRewrite:" + isRewrite);
? ? ? ? ? ? ? ? ? ? ? ? int time = (int) (System.currentTimeMillis() - startTime);
? ? ? ? ? ? ? ? ? ? ? ? Log.e("Test", "run------ time: " + time);
? ? ? ? ? ? ? ? ? ? ? ? Log.e("Test", "run------ bytes:" + arrayOfByte.length);
? ? ? ? ? ? ? ? ? ? ? ? Log.e("Test", "run------ getFilePointer:" + randomAccessFile.getFilePointer());
? ? ? ? ? ? ? ? ? ? ? ? startPos = (int) randomAccessFile.getFilePointer();
? ? ? ? ? ? ? ? ? ? ? ? if (time >= restOfTime) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? stopRecord();
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? onFileChangedListener.onBufferRecevied(startPos, endPos, arrayOfByte, curFileLength, isRewrite);
// ? ? ? ? ? ? ? ? ? ? ? ?Test t1 = Test.this;
// ? ? ? ? ? ? ? ? ? ? ? ?t1.i = 1+t1.i;
//
// ? ? ? ? ? ? ? ? ? ? ? ?long length = randomAccessFile.getFilePointer() + arrayOfByte.length;
// ? ? ? ? ? ? ? ? ? ? ? ?xxx(this,length);
// ? ? ? ? ? ? ? ? ? ? ? ?if (endPos>curFileLength){
// ? ? ? ? ? ? ? ? ? ? ? ? ? ?xxx(this,endPos);
// ? ? ? ? ? ? ? ? ? ? ? ?}
// ? ? ? ? ? ? ? ? ? ? ? ?randomAccessFile.write(arrayOfByte);
// ? ? ? ? ? ? ? ? ? ? ? ?onFileChangedListener.onBufferRecevied(startPos,endPos,arrayOfByte,curFileLength,isRewrite);
//
// ? ? ? ? ? ? ? ? ? ? ? ?Test t2 =Test.this;
// ? ? ? ? ? ? ? ? ? ? ? ?t2.j = 1+t2.j;
// ? ? ? ? ? ? ? ? ? ? ? ?if (i!=0||j!=0){
// ? ? ? ? ? ? ? ? ? ? ? ? ? ?continue;
// ? ? ? ? ? ? ? ? ? ? ? ?}
// ? ? ? ? ? ? ? ? ? ? ? ?xxx(this,false);
// ? ? ? ? ? ? ? ? ? ? ? ?errorHandler.sendEmptyMessage(0);
? ? ? ? ? ? ? ? ? ? } catch (Exception e) {
? ? ? ? ? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? ? ? ? ? ? ? errorHandler.sendEmptyMessage(0);
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? };
? ? ? ? thread.start();
? ? }
? ? private byte[] pjArray(byte[] src, byte[] dest) {
? ? ? ? byte[] newBytes = new byte[src.length + dest.length];
? ? ? ? System.arraycopy(src, 0, newBytes, 0, src.length);
? ? ? ? System.arraycopy(dest, 0, newBytes, src.length, dest.length);
? ? ? ? return newBytes;
? ? }
? ? private void pjByteBuffer(byte[] tmpBuf) {
? ? ? ? if (sampleBytes == null) {
? ? ? ? ? ? sampleBytes = tmpBuf;
? ? ? ? } else {
? ? ? ? ? ? sampleBytes = pjArray(sampleBytes, tmpBuf);
? ? ? ? }
? ? }
? ? private void pauseReview() {
? ? ? ? audioTrackFlag = false;
? ? ? ? audioTrack.pause();
? ? ? ? audioTrack.flush();
? ? ? ? Log.e("Test", "pauseReview------ ");
? ? }
? ? private void startReview(boolean b) {
? ? ? ? if (audioTrack == null) {
? ? ? ? ? ? initAudioTack();
? ? ? ? }
? ? ? ? audioTrack.play();
? ? ? ? audioTrackFlag = true;
? ? ? ? audioTrackThread = new Thread() {
? ? ? ? ? ? @Override
? ? ? ? ? ? public void run() {
? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? bytes = new byte[minBufferSize];
? ? ? ? ? ? ? ? ? ? while (randomAccessFile.read(bytes) != -1 && audioTrackFlag) {
? ? ? ? ? ? ? ? ? ? ? ? Log.e("Test", "run------ ");
? ? ? ? ? ? ? ? ? ? ? ? Log.e("Test", "run------audiotrackflag is ?" + audioTrackFlag);
? ? ? ? ? ? ? ? ? ? ? ? Log.e("Test", "run------wrtie data in audiotrack ");
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? Log.e("Test", "run------ audiotrack end.");
? ? ? ? ? ? ? ? } catch (Exception e) {
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? };
? ? ? ? audioTrackThread.start();
? ? }
? ? public void pauseRecord() {
? ? ? ? if (audioRecord != null) {
? ? ? ? ? ? audioRecord.stop();
? ? ? ? ? ? mThreadFlag = false;
? ? ? ? }
? ? }
? ? private void reset() {
? ? ? ? startPos = 0;
? ? ? ? endPos = 0;
? ? ? ? curFileLength = 44;
? ? ? ? isRewrite = false;
? ? }
? ? private void resumeRecord() {
? ? ? ? while (isRewrite) {//写文件
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? if (randomAccessFile.getFilePointer() != endPos || !isRewrite) {//不可写 ,或者选中位置不是文件指针所在位置
? ? ? ? ? ? ? ? ? ? startPos = (int) randomAccessFile.getFilePointer(); // 从文件指针位置开始
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? if (!isRewrite) {//不写文件
? ? ? ? ? ? ? ? ? ? if (44 + startPos >= endPos) {//
? ? ? ? ? ? ? ? ? ? ? ? continue;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? isRewrite = true;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? } catch (IOException e) {
? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? }
? ? ? ? ? ? audioRecord.startRecording();
? ? ? ? ? ? if (thread == null || !mThreadFlag) {
? ? ? ? ? ? ? ? startThread();
? ? ? ? ? ? }
? ? ? ? }
? ? }
? ? private void startRecording() {
? ? ? ? try {
? ? ? ? ? ? audioRecord.startRecording();
// ? ? ? ? ? ?if (thread==null||!mThreadFlag){
// ? ? ? ? ? ? ? ?startThread();
// ? ? ? ? ? ?}
? ? ? ? ? ? startThread();
? ? ? ? ? ? Log.e("Test", "startRecording------ ");
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? initRecorder(true);
? ? ? ? }
? ? }
? ? public int getMinBufferSize() {
? ? ? ? return minBufferSize;
? ? }
? ? public void getMinBufSize() {
? ? ? ? recBufSize = AudioRecord.getMinBufferSize(frequency, 12, 2);
? ? }
? ? public void setOnFileChangedListener(OnFileChangedListener onFileChangedListener) {
? ? ? ? this.onFileChangedListener = onFileChangedListener;
? ? }
? ? public void setDuration(int duration) {
? ? ? ? this.duration = duration;
? ? }
? ? interface OnFileChangedListener {
? ? ? ? void onBufferRecevied(long startPos, long endPos, byte[] b1, long currenFileLength, boolean isRew);
? ? }
? ? interface OnEventListener {
? ? ? ? void onError(String s);
? ? ? ? void onRecordComplete();
? ? ? ? void onVolumnChanged(double vl);
? ? }
? ? public String creatFile() {
// ? ? ? ?copyWaveFile(filePath,filePath,true);
? ? ? ? return outPath;
? ? }
? ? private void moveToPrevious(long pl) {
? ? ? ? try {
? ? ? ? ? ? long l = 44 + 4 * (pl * rate / 1000l);
? ? ? ? ? ? randomAccessFile.seek(l);
? ? ? ? ? ? Log.e("Test", "moveToPrevious------ offset:" + l + " length:" + randomAccessFile.length());
? ? ? ? } catch (Exception e) {
? ? ? ? }
? ? }
? ? @Override
? ? protected void onPause() {
? ? ? ? super.onPause();
? ? ? ? stopRecord();
? ? }
? ? @Override
? ? protected void onStop() {
? ? ? ? super.onStop();
? ? ? ? closeStream();
? ? }
? ? private void stopRecord() {
? ? ? ? try {
? ? ? ? ? ? mThreadFlag = false;
? ? ? ? ? ? time = 0;
? ? ? ? ? ? thread = null;
? ? ? ? ? ? if (audioRecord != null) {
? ? ? ? ? ? ? ? if (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
? ? ? ? ? ? ? ? ? ? audioRecord.stop();
? ? ? ? ? ? ? ? ? ? Log.e("Test", "stopRecord------ ");
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? audioRecord.release();
? ? ? ? ? ? ? ? audioRecord = null;
? ? ? ? ? ? }
? ? ? ? ? ? closeStream();
? ? ? ? } catch (Exception e) {
? ? ? ? }
? ? }
? ? private void closeStream() {
? ? ? ? try {
? ? ? ? ? ? if (randomAccessFile != null) {
? ? ? ? ? ? ? ? randomAccessFile.close();
? ? ? ? ? ? }
? ? ? ? } catch (Exception e) {
? ? ? ? }
? ? }
? ? float getDensity() {
? ? ? ? DisplayMetrics metrics = new DisplayMetrics();
? ? ? ? getWindowManager().getDefaultDisplay().getMetrics(metrics);
? ? ? ? return metrics.density;
? ? }
}
来源:https://blog.csdn.net/aikongmeng/article/details/50427678
标签:Android,录音,声波图
0
投稿
猜你喜欢
c#调用c++的DLL的实现方法
2023-10-27 05:27:40
C#客户端程序调用外部程序的3种实现方法
2023-06-02 11:39:10
深入解析Android系统中应用程序前后台切换的实现要点
2022-09-11 01:26:30
Spring Boot打war包的实例教程
2022-10-05 07:52:49
JAVA遍历map的几种实现方法代码
2023-08-27 22:06:04
WPF使用WrapPanel实现虚拟化效果
2021-07-04 14:53:53
ANDROID中自定义对话框AlertDialog使用示例
2023-07-22 17:24:12
Android播放多张图片形成的一个动画示例
2021-08-23 18:07:32
c++中数字与字符串之间的转换方法(推荐)
2023-02-22 17:58:09
Java求一个分数数列的前20项之和的实现代码
2021-08-22 14:59:58
Android gradle打包并自动上传的方法
2022-01-23 06:50:37
用C#缩小照片上传到各种空间的具体方法
2022-03-28 02:29:18
Java中ReentrantLock4种常见的坑
2021-09-26 10:51:46
C++定义和初始化string对象实例详解
2022-08-05 06:03:00
android解析JSON数据
2022-10-17 04:55:40
java中this的n种使用方法
2023-12-23 13:13:23
MyBatis动态SQL特性详解
2022-10-03 03:21:50
Android Volley框架使用方法详解
2022-04-07 09:06:49
Android RecyclerView详解之实现 ListView GridView瀑布流效果
2023-11-26 10:09:00
c#测试反射性能示例
2021-12-19 23:13:47