android音频编辑之音频裁剪的示例代码

前言

本篇开始讲解音频编辑的具体操作,从相对简单的音频裁剪开始。要进行音频裁剪,我的方案是开启一个Service服务用于音频裁剪的耗时操作,主界面发送裁剪命令,同时注册EventBus接受裁剪的消息(当然也可以使用广播接受的方式)。因此,在本篇主要会讲解以下内容:

  1. 音频编辑项目的整体结构
  2. 音频裁剪方法的流程实现
  3. 获取音频文件相关信息
  4. 计算裁剪时间点对应文件中数据的位置
  5. 写入wav文件头信息
  6. 写入wav文件裁剪部分的音频数据

下面是音频裁剪效果图:

音频编辑项目的整体结构

该音频测试项目的结构其实很简单,大致就是以Fragment为基础的各个界面,以IntentService为基础的后台服务,以及最重要的音频编辑工具类实现。大致结构如下:

  1. CutFragment,裁剪页面。选择音频,裁剪音频,播放裁剪后的音频,同时注册了EventBus以便接受后台音频编辑操作发送的消息进行更新。
  2. AudioTaskService,音频编辑服务Service。继承自IntentService,可以在后台任务的线程中执行耗时音频编辑操作。
  3. AudioTaskCreator,音频编辑任务命令发送器。通过它可以启动音频编辑服务AudioTaskService,并发送具体的编辑操作给它。
  4. AudioTaskHandler,音频编辑任务处理器。AudioTaskService接受到的intent任务都交给它去处理。这里具体处理裁剪,合成等操作。
  5. AudioEditUtil, 音频编辑工具类。提供裁剪,合成等音频编辑的方法。
  6. 另外还有其他相关的音频工具类。

现在我们看看它们之间的主要流程实现:

CutFragment发起音频裁剪任务,同时接收更新音频编辑消息

public class CutFragment extends Fragment {

 ...

 /**
  * 裁剪音频
  */
 private void cutAudio() {

  String path1 = tvAudioPath1.getText().toString();

  if(TextUtils.isEmpty(path1)){
   ToastUtil.showToast("音频路径为空");
   return;
  }

  float startTime = Float.valueOf(etStartTime.getText().toString());
  float endTime = Float.valueOf(etEndTime.getText().toString());

  if(startTime <= 0){
   ToastUtil.showToast("时间不对");
   return;
  }
  if(endTime <= 0){
   ToastUtil.showToast("时间不对");
   return;
  }
  if(startTime >= endTime){
   ToastUtil.showToast("时间不对");
   return;
  }

  //调用AudioTaskCreator发起音频裁剪任务
  AudioTaskCreator.createCutAudioTask(getContext(), path1, startTime, endTime);
 }

 /**
  * 接收并更新裁剪消息
  */
 @Subscribe(threadMode = ThreadMode.MAIN) public void onReceiveAudioMsg(AudioMsg msg) {
  if(msg != null && !TextUtils.isEmpty(msg.msg)){
   tvMsgInfo.setText(msg.msg);
   mCurPath = msg.path;
  }
 }

}

AudioTaskCreator启动音频裁剪任务AudioTaskService

public class AudioTaskCreator {
 ...
 /**
  * 启动音频裁剪任务
  * @param context
  * @param path
  */
 public static void createCutAudioTask(Context context, String path, float startTime, float endTime){

  Intent intent = new Intent(context, AudioTaskService.class);
  intent.setAction(ACTION_AUDIO_CUT);
  intent.putExtra(PATH_1, path);
  intent.putExtra(START_TIME, startTime);
  intent.putExtra(END_TIME, endTime);

  context.startService(intent);
 }

}

AudioTaskService服务将接受的Intent任务交给AudioTaskHandler处理

/**
 * 执行后台任务的服务
 */
public class AudioTaskService extends IntentService {

 private AudioTaskHandler mTaskHandler;

 public AudioTaskService() {
  super("AudioTaskService");
 }

 @Override public void onCreate() {
  super.onCreate();

  mTaskHandler = new AudioTaskHandler();
 }

 /**
  * 实现异步任务的方法
  *
  * @param intent Activity传递过来的Intent,数据封装在intent中
  */
 @Override protected void onHandleIntent(Intent intent) {

  if (mTaskHandler != null) {
   mTaskHandler.handleIntent(intent);
  }
 }
}

AudioTaskService服务将接受的Intent任务交给AudioTaskHandler处理,根据不同的Intent action,调用不同的处理方法

/**
 *
 */
public class AudioTaskHandler {
 public void handleIntent(Intent intent){
  if(intent == null){
   return;
  }

  String action = intent.getAction();

  switch (action){
   case AudioTaskCreator.ACTION_AUDIO_CUT:

   {
    //裁剪
    String path = intent.getStringExtra(AudioTaskCreator.PATH_1);
    float startTime = intent.getFloatExtra(AudioTaskCreator.START_TIME, 0);
    float endTime = intent.getFloatExtra(AudioTaskCreator.END_TIME, 0);
    cutAudio(path, startTime, endTime);
   }
    break;

    //其他编辑任务
    ...

   default:
   break;
  }
 }
 /**
  * 裁剪音频
  * @param srcPath 源音频路径
  * @param startTime 裁剪开始时间
  * @param endTime 裁剪结束时间
  */
 private void cutAudio(String srcPath, float startTime, float endTime){
  //具体裁剪操作
 }
}

音频裁剪方法的实现

接下来是音频裁剪的具体操作。还记得上一篇文章说的,音频的裁剪操作都是要基于PCM文件或者WAV文件上进行的,所以对于一般的音频文件都是需要先解码得到PCM文件或者WAV文件,才能进行具体的音频编辑操作。因此音频裁剪操作需要经历以下步骤:

  1. 计算解码后的wav音频路径
  2. 对源音频进行解码,得到解码后源WAV文件
  3. 创建源wav文件和目标WAV音频频的RandomAccessFile,以便对它们后面对它们进行读写操作
  4. 根据采样率,声道数,采样位数,和当前时间,计算开始时间和结束时间对应到源文件的具体位置
  5. 根据采样率,声道数,采样位数,裁剪音频数据大小等,计算得到wav head文件头byte数据
  6. 将wav head文件头byte数据写入到目标文件中
  7. 将源文件的开始位置到结束位置的数据复制到目标文件中
  8. 删除源wav文件,重命名目标wav文件为源wav文件,即得到最终裁剪后的wav文件

如下,对源音频进行解码,得到解码后的音频文件,然后根据解码音频文件得到Audio音频相关信息,里面记录音频相关的信息如采样率,声道数,采样位数等。

/**
 *
 */
public class AudioTaskHandler {

 /**
  * 裁剪音频
  * @param srcPath 源音频路径
  * @param startTime 裁剪开始时间
  * @param endTime 裁剪结束时间
  */
 private void cutAudio(String srcPath, float startTime, float endTime){
  String fileName = new File(srcPath).getName();
  String nameNoSuffix = fileName.substring(0, fileName.lastIndexOf('.'));
  fileName = nameNoSuffix + Constant.SUFFIX_WAV;
  String outName = nameNoSuffix + "_cut.wav";

  //裁剪后音频的路径
  String destPath = FileUtils.getAudioEditStorageDirectory() + File.separator + outName;

  //解码源音频,得到解码后的文件
  decodeAudio(srcPath, destPath);

  if(!FileUtils.checkFileExist(destPath)){
   ToastUtil.showToast("解码失败" + destPath);
   return;
  }

  //获取根据解码后的文件得到audio数据
  Audio audio = getAudioFromPath(destPath);

  //裁剪操作
  if(audio != null){
   AudioEditUtil.cutAudio(audio, startTime, endTime);
  }

  //裁剪完成,通知消息
  String msg = "裁剪完成";
  EventBus.getDefault().post(new AudioMsg(AudioTaskCreator.ACTION_AUDIO_CUT, destPath, msg));
 }

 /**
  * 获取根据解码后的文件得到audio数据
  * @param path
  * @return
  */
 private Audio getAudioFromPath(String path){
  if(!FileUtils.checkFileExist(path)){
   return null;
  }

  if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
   try {
    Audio audio = Audio.createAudioFromFile(new File(path));
    return audio;
   } catch (Exception e) {
    e.printStackTrace();
   }
  }
  return null;
 }
}

获取音频文件相关信息

而获取Audio信息其实就是解码时获取MediaFormat,然后获取音频相关的信息的。

/**
 * 音频信息
 */
public class Audio {
  private String path;
  private String name;
  private float volume = 1f;
  private int channel = 2;
  private int sampleRate = 44100;
  private int bitNum = 16;
  private int timeMillis;

  ...

  @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) public static Audio createAudioFromFile(File inputFile) throws Exception {
    MediaExtractor extractor = new MediaExtractor();
    MediaFormat format = null;
    int i;

    try {
      extractor.setDataSource(inputFile.getPath());
    }catch (Exception ex){
      ex.printStackTrace();
      extractor.setDataSource(new FileInputStream(inputFile).getFD());
    }

    int numTracks = extractor.getTrackCount();
    for (i = 0; i < numTracks; i++) {
      format = extractor.getTrackFormat(i);
      if (format.getString(MediaFormat.KEY_MIME).startsWith("audio/")) {
        extractor.selectTrack(i);
        break;
      }
    }
    if (i == numTracks) {
      throw new Exception("No audio track found in " + inputFile);
    }

    Audio audio = new Audio();
    audio.name = inputFile.getName();
    audio.path = inputFile.getAbsolutePath();
    audio.sampleRate = format.containsKey(MediaFormat.KEY_SAMPLE_RATE) ? format.getInteger(MediaFormat.KEY_SAMPLE_RATE) : 44100;
    audio.channel = format.containsKey(MediaFormat.KEY_CHANNEL_COUNT) ? format.getInteger(MediaFormat.KEY_CHANNEL_COUNT) : 1;
    audio.timeMillis = (int) ((format.getLong(MediaFormat.KEY_DURATION) / 1000.f));

    //根据pcmEncoding编码格式,得到采样精度,MediaFormat.KEY_PCM_ENCODING这个值不一定有
    int pcmEncoding = format.containsKey(MediaFormat.KEY_PCM_ENCODING) ? format.getInteger(MediaFormat.KEY_PCM_ENCODING) : AudioFormat.ENCODING_PCM_16BIT;
    switch (pcmEncoding){
      case AudioFormat.ENCODING_PCM_FLOAT:
        audio.bitNum = 32;
        break;
      case AudioFormat.ENCODING_PCM_8BIT:
        audio.bitNum = 8;
        break;
      case AudioFormat.ENCODING_PCM_16BIT:
      default:
        audio.bitNum = 16;
        break;
    }

    extractor.release();

    return audio;
  }
}

这里要注意,通过MediaFormat获取音频信息的时候,获取采样位数是要先查找MediaFormat.KEY_PCM_ENCODING这个key对应的值,如果是AudioFormat.ENCODING_PCM_8BIT,则是8位采样精度,如果是AudioFormat.ENCODING_PCM_16BIT,则是16位采样精度,如果是AudioFormat.ENCODING_PCM_FLOAT(android 5.0 版本新增的类型),则是32位采样精度。当然可能MediaFormat中没有包含MediaFormat.KEY_PCM_ENCODING这个key信息,这时就使用默认的AudioFormat.ENCODING_PCM_16BIT,即默认的16位采样精度(也可以说2个字节作为一个采样点编码)。

接下来就是真正的裁剪操作了。根据audio中的音频信息得到将要写入的wav文件头信息字节数据,创建随机读写文件,写入文件头数据,然后源随机读写文件移动到指定的开始时间开始读取,目标随机读写文件将读取的数据写入,知道源随机文件读到指定的结束时间停止,这样就完成了音频文件的裁剪操作。

public class AudioEditUtil {
 /**
  * 裁剪音频
  * @param audio 音频信息
  * @param cutStartTime 裁剪开始时间
  * @param cutEndTime 裁剪结束时间
  */
 public static void cutAudio(Audio audio, float cutStartTime, float cutEndTime){
  if(cutStartTime == 0 && cutEndTime == audio.getTimeMillis() / 1000f){
   return;
  }
  if(cutStartTime >= cutEndTime){
   return;
  }

  String srcWavePath = audio.getPath();
  int sampleRate = audio.getSampleRate();
  int channels = audio.getChannel();
  int bitNum = audio.getBitNum();
  RandomAccessFile srcFis = null;
  RandomAccessFile newFos = null;
  String tempOutPath = srcWavePath + ".temp";
  try {

   //创建输入流
   srcFis = new RandomAccessFile(srcWavePath, "rw");
   newFos = new RandomAccessFile(tempOutPath, "rw");

   //源文件开始读取位置,结束读取文件,读取数据的大小
   final int cutStartPos = getPositionFromWave(cutStartTime, sampleRate, channels, bitNum);
   final int cutEndPos = getPositionFromWave(cutEndTime, sampleRate, channels, bitNum);
   final int contentSize = cutEndPos - cutStartPos;

   //复制wav head 字节数据
   byte[] headerData = AudioEncodeUtil.getWaveHeader(contentSize, sampleRate, channels, bitNum);
   copyHeadData(headerData, newFos);

   //移动到文件开始读取处
   srcFis.seek(WAVE_HEAD_SIZE + cutStartPos);

   //复制裁剪的音频数据
   copyData(srcFis, newFos, contentSize);

  } catch (Exception e) {
   e.printStackTrace();

   return;

  }finally {
   //关闭输入流
   if(srcFis != null){
    try {
     srcFis.close();
    } catch (IOException e) {
     e.printStackTrace();
    }
   }
   if(newFos != null){
    try {
     newFos.close();
    } catch (IOException e) {
     e.printStackTrace();
    }
   }
  }

  // 删除源文件,
  new File(srcWavePath).delete();
  //重命名为源文件
  FileUtils.renameFile(new File(tempOutPath), audio.getPath());
 }
}

计算裁剪时间点对应文件中数据的位置

需要注意的是根据时间计算在文件中的位置,它是这么实现的:

 /**
  * 获取wave文件某个时间对应的数据位置
  * @param time 时间
  * @param sampleRate 采样率
  * @param channels 声道数
  * @param bitNum 采样位数
  * @return
  */
 private static int getPositionFromWave(float time, int sampleRate, int channels, int bitNum) {
  int byteNum = bitNum / 8;
  int position = (int) (time * sampleRate * channels * byteNum);

  //这里要特别注意,要取整(byteNum * channels)的倍数
  position = position / (byteNum * channels) * (byteNum * channels);

  return position;
 }

这里要特别注意,因为time是个float的数,所以计算后的position取整它并不一定是(byteNum * channels)的倍数,而position的位置必须要是(byteNum * channels)的倍数,否则后面的音频数据就全部乱了,那么在播放时就是撒撒撒撒的噪音,而不是原来的声音了。原因是音频数据是按照一个个采样点来计算的,一个采样点的大小就是(byteNum * channels),所以要取(byteNum * channels)的整数倍。

写入wav文件头信息

接着看看往新文件写入wav文件头是怎么实现的,这个在上一篇中也是有讲过的,不过还是列出来吧:

/**
  * 获取Wav header 字节数据
  * @param totalAudioLen 整个音频PCM数据大小
  * @param sampleRate 采样率
  * @param channels 声道数
  * @param bitNum 采样位数
  * @throws IOException
  */
 public static byte[] getWaveHeader(long totalAudioLen, int sampleRate, int channels, int bitNum) throws IOException {

  //总大小,由于不包括RIFF和WAV,所以是44 - 8 = 36,在加上PCM文件大小
  long totalDataLen = totalAudioLen + 36;
  //采样字节byte率
  long byteRate = sampleRate * channels * bitNum / 8;

  byte[] header = new byte[44];
  header[0] = 'R'; // RIFF
  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';//WAVE
  header[9] = 'A';
  header[10] = 'V';
  header[11] = 'E';
  //FMT Chunk
  header[12] = 'f'; // 'fmt '
  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;
  //编码方式 10H为PCM编码格式
  header[20] = 1; // format = 1
  header[21] = 0;
  //通道数
  header[22] = (byte) channels;
  header[23] = 0;
  //采样率,每个通道的播放速度
  header[24] = (byte) (sampleRate & 0xff);
  header[25] = (byte) ((sampleRate >> 8) & 0xff);
  header[26] = (byte) ((sampleRate >> 16) & 0xff);
  header[27] = (byte) ((sampleRate >> 24) & 0xff);
  //音频数据传送速率,采样率*通道数*采样深度/8
  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) (channels * 16 / 8);
  header[33] = 0;
  //每个样本的数据位数
  header[34] = 16;
  header[35] = 0;
  //Data chunk
  header[36] = 'd';//data
  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;
 }

这里比上一篇中精简了一些,只要传入音频数据大小,采样率,声道数,采样位数这四个参数,就可以得到wav文件头信息了,然后再将它写入到wav文件开始处。

/**
  * 复制wav header 数据
  *
  * @param headerData wav header 数据
  * @param fos 目标输出流
  */
 private static void copyHeadData(byte[] headerData, RandomAccessFile fos) {
  try {
   fos.seek(0);
   fos.write(headerData);
  } catch (Exception ex) {
   ex.printStackTrace();
  }
 }

写入wav文件裁剪部分的音频数据

接下来就是将裁剪部分的音频数据写入到文件中了。这里要先移动源文件的读取位置到裁剪起始处,即

//移动到文件开始读取处
srcFis.seek(WAVE_HEAD_SIZE + cutStartPos);

这样就可以从源文件读取裁剪处的数据了

 /**
  * 复制数据
  *
  * @param fis 源输入流
  * @param fos 目标输出流
  * @param cooySize 复制大小
  */
 private static void copyData(RandomAccessFile fis, RandomAccessFile fos, final int cooySize) {

  byte[] buffer = new byte[2048];
  int length;
  int totalReadLength = 0;

  try {

   while ((length = fis.read(buffer)) != -1) {

    fos.write(buffer, 0, length);

    totalReadLength += length;

    int remainSize = cooySize - totalReadLength;
    if (remainSize <= 0) {
     //读取指定位置完成
     break;
    } else if (remainSize < buffer.length) {
     //离指定位置的大小小于buffer的大小,换remainSize的buffer
     buffer = new byte[remainSize];
    }
   }
  } catch (Exception ex) {
   ex.printStackTrace();
  }
 }

上面代码目的就是读取startPos开始,到startPos+copySize之间的数据。

总结

到这里的话,想必对裁剪的整体流程有一定的了解了,总结起来的话,首先是对音频解码,得到解码后的wav文件或者pcm文件,然后取得音频的文件头信息(包括采样率,声道数,采样位数,时间等),然后计算得到裁剪时间对应到文件中数据位置,以及裁剪的数据大小,然后计算得到裁剪后的wav文件头信息,并写入新文件中,最后将源文件裁剪部分的数据写入到新文件中,最终得到裁剪后的wav文件了。

读者可能会有疑问,我想要裁剪的是mp3文件,这里只是得到裁剪后的wav文件,那怎么得到裁剪后的mp3文件呢?这个就需要对该wav文件进行mp3编码压缩了,具体实现可以参考我的Github项目 AudioEdit

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

您可能感兴趣的文章:

  • Android音频录制MediaRecorder之简易的录音软件实现代码
  • Android提高之MediaPlayer播放网络音频的实现方法
  • Android音频可视化开发案例说明
  • Android使用音频信息绘制动态波纹
  • Android音频处理之通过AudioRecord去保存PCM文件进行录制,播放,停止,删除功能
  • android downsample降低音频采样频率代码
  • Android实现音频条形图效果(仿音频动画无监听音频输入)
  • Android App中使用AudioManager类来编写音频播放器
  • Android音频系统AudioTrack使用方法详解
(0)

相关推荐

  • Android音频处理之通过AudioRecord去保存PCM文件进行录制,播放,停止,删除功能

    音频这方面很博大精深,我这里肯定讲不了什么高级的东西,最多也只是一些基础类知识,首先,我们要介绍一下Android他提供的录音类,实际上他有两个,一个是MediaRecorder,还有一个就是我们今天要用到的AudioRecord,那他们有什么区别呢? 一.区别 MediaRecorder和AudioRecord都可以录制音频,区别是MediaRecorder录制的音频文件是经过压缩后的,需要设置编码器.并且录制的音频文件可以用系统自带的Music播放器播放. 而AudioRecord录制的是P

  • Android音频录制MediaRecorder之简易的录音软件实现代码

    使用MediaRecorder的步骤:1.创建MediaRecorder对象2.调用MediRecorder对象的setAudioSource()方法设置声音的来源,一般传入MediaRecorder.MIC3.调用MediaRecorder对象的setOutputFormat()设置所录制的音频文件的格式4.调用MediaRecorder对象的setAudioRncoder().setAudioEncodingBitRate(int bitRate).setAudioSamlingRate(i

  • Android音频可视化开发案例说明

    Android 调用自带的录制音频程序 Android中有自带的音频录制程序,我们可以通过指定一个Action MediaStore.Audio.Media.RECORD_SOUND_ACTION的Intent来 启动它就可以了.然后在onActivityResult()方法中,获取Intent的Data,就是录制的音频对应的URI. java代码: 复制代码 代码如下: package eoe.demo; import android.app.Activity; import android.

  • android downsample降低音频采样频率代码

    使用Android AudioRecord 录制PCM文件,android SDK保证在所有设备上都支持的采样频率只有44100HZ,所以如果想得到其他采样频率的PCM数据,有几种方式:1.在设备上尝试可用的采样频率,2.使用44.1K采样后转换采样频率. 其中第二种转换采样频率的操作,有很多种方法.目前我使用的是SSRC,效果很好. 复制代码 代码如下: private void simpleDownSample() {        File BeforeDownSampleFile = n

  • Android音频系统AudioTrack使用方法详解

    今天,简单讲讲AudioTrack的使用方法. 1.Android AudioTrack简介 在android中播放声音可以用MediaPlayer和AudioTrack两种方案的,但是两种方案是有很大区别的,MediaPlayer可以播放多种格式的声音文件,例如MP3,AAC,WAV,OGG,MIDI等.而AudioTrack只能播放PCM数据流. 事实上,两种本质上是没啥区别的,MediaPlayer在播放音频时,在framework层还是会创建AudioTrack,把解码后的PCM数流传递

  • Android实现音频条形图效果(仿音频动画无监听音频输入)

    音频条形图 如下图所示就是这次的音频条形图: 由于只是自定义View的用法,我们就不去真实地监听音频输入了,随机模拟一些数字即可. 如果要实现一个如上图的静态音频条形图,相信大家应该可以很快找到思路,也就是绘制一个个的矩形,每个矩形之间稍微偏移一点距离即可.如下代码就展示了一种计算坐标的方法. for (int i = 0; i < mRectCount; i++) { // 矩形的绘制是从左边开始到上.右.下边(左右边距离左边画布边界的距离,上下边距离上边画布边界的距离) canvas.dra

  • Android提高之MediaPlayer播放网络音频的实现方法

    前面有文章曾经地介绍过MediaPlayer的基本用法,这里就更加深入地讲解MediaPlayer的在线播放功能.本文主要实现MediaPlayer在线播放音频的功能,由于在线视频播放比在线音频播放复杂,因此先介绍在线音频播放的实现,这样可以帮助大家逐步深入了解MediaPlayer的在线播放功能. 先来看看本文程序运行的结果如下图所示: main.xml的源码如下: <?xml version="1.0" encoding="utf-8"?> <

  • Android App中使用AudioManager类来编写音频播放器

    手机都有声音模式,声音.静音还有震动,甚至震动加声音兼备,这些都是手机的基本功能.在Android手机中,我们同样可以通过Android的SDK提供的声音管理接口来管理手机声音模式以及调整声音大小,这就是Android中AudioManager的使用. AudioManager 类位于 android.Media 包中,该类提供访问控制音量和钤声模式的操作   以下分别是AudioManager设置声音模式和调整声音大小的方法.     如何获取声音管理器: AudioManager audio

  • Android使用音频信息绘制动态波纹

    在一些音乐类应用中, 经常会展示随着节奏上下起伏的波纹信息, 这些波纹形象地传达了声音信息, 可以提升用户体验, 那么是如何实现的呢? 可以使用Visualizer类获取当前播放的声音信息, 并绘制在画布上, 使用波纹展示即可. 我来讲解一下使用方法. 主要 (1) Visualizer类提取波纹信息的方式. (2) 应用动态权限管理的方法. (3) 分离自定义视图的展示和逻辑. 1. 基础准备 Android 6.0引入动态权限管理, 在这个项目中, 会使用系统的音频信息, 因此把权限管理引入

  • Java实现音频添加自定义时长静音的示例代码

    目录 前言 Maven依赖 代码 验证一下 前言 本文提供一个可以给一个wav音频添加自定义时长静音的工具类.正好工作中用到,所以正好分享分享. Maven依赖 <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>30.1.1-jre</version> </dependency> &l

  • Android利用Flutter path绘制粽子的示例代码

    目录 前言 绘制 基本轮廓 粽叶 嘴巴 眼睛 腮红 手&脚 头巾 咸甜是一家 发声 动画控制嘴巴开合 用到的技术点 总结 前言 大家好,端午将至,首先提前祝小伙伴端午安康,端午作为中华民族的非常重要的传统节日,粽子那是必不可少的,但是你真的知道粽子的历史吗? 今天跟随本篇文章用Flutter path画一个会科普节日的的粽子吧- 绘制 基本轮廓 首先我们需要将粽子的基本轮廓绘制出来,通过图片可以看到粽子的轮廓是一个圆圆的三角形状, 本篇文章所有的图形都是用纯Path路径制作,这里我们可以将粽子的

  • Android串口通信封装之OkUSB的示例代码

    本文介绍了Android串口通信封装之OkUSB的示例代码,分享给大家.具体如下: Github传送门:OkUSB OkUSB 一个简洁的Android串口通信框架. 功能简介 支持设置波特率 支持设置数据位 支持设置停止位 支持设置校验位 支持DTS和RTS 支持串口连接状态监听 用法简介 Gradle allprojects { repositories { ... maven { url 'https://jitpack.io' } } } dependencies { compile '

  • Android 实现无网络页面切换的示例代码

    本文介绍了Android 实现无网络页面切换的示例代码,分享给大家,具体如下: 实现思路 需求是在无网络的时候显示特定的页面,想到要替换页面的地方,大多都是recyclerview或者第三方recyclerview这种需要显示数据的地方,因此决定替换掉页面中所有的recyclerview为无网络页面 实现过程 1 在BaseActivity中,当加载布局成功以后,通过id找到要替换的view,通过indexOfChild()方法,找到要替换的view的位置,再通过remove和add view来

  • Android ItemDecoration 实现分组索引列表的示例代码

    本文介绍了Android ItemDecoration 实现分组索引列表的示例代码,分享给大家.具体如下: 先来看看效果: 我们要实现的效果主要涉及三个部分: 分组 GroupHeader 分割线 SideBar 前两个部分涉及到一个ItemDecoration类,也是我们接下来的重点,该类是RecyclerView的一个抽象静态内部类,主要作用就是给RecyclerView的ItemView绘制额外的装饰效果,例如给RecyclerView添加分割线. 使用ItemDecoration时需要继

  • Android中js和原生交互的示例代码

    本文介绍了Android中js和原生交互的示例代码,分享给大家,具体如下: 加载webview的类 public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); JavaScriptInterf

  • Android中TabLayout添加小红点的示例代码

    本文介绍了Android中TabLayout添加小红点的示例代码,分享给大家,具体如下 安卓原生的android.support.design.widget.TabLayout,配合ViewPager已经很好用了,但是有时我们会在内容更新时,在tab标题右上方加上一个红点等标记此tab内容有更新时,就需要给原生的TabLayout设置你定义的布局,用法和原生的一样,只是在代码中设置一下TabLayout的布局. 1.主布局文件 <?xml version="1.0" encodi

  • Android 快速实现状态栏透明样式的示例代码

    在手机 app 开发过程中,经常会遇到一种需求,需要将 内容区域 顶到 状态栏 中去.这个时候,下面一段代码,就能很轻松解决问题了. 上代码之前先上效果图: 下面上一段代码: getWindow().requestFeature(Window.FEATURE_NO_TITLE); if(VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { Window window = getWindow(); window.clearFlags(WindowManager.

  • Android监听手机短信的示例代码

    本文介绍了Android监听手机短信的示例代码,分享给大家,具体如下: 以下情况可能会导致短信拦截失败: 小米,360等品牌手机拦截短信,短信的优先级给了系统 用户禁用短信权限 手机连接电脑,被电脑端的手机助手类软件截获 手机内装有QQ通讯录之类的管理联系人,短信的应用,被截获. 前提--权限: <uses-permission android:name="android.permission.RECEIVE_SMS" > </uses-permission>

  • Android 实现无网络传输文件的示例代码

    最近的项目需要实现一个 Android 手机之间无网络传输文件的功能,就发现了 Wifi P2P(Wifi点对点)这么一个功能,最后也实现了通过 Wifi 隔空传输文件 的功能,这里我也来整理下代码,分享给大家. Wifi P2P 是在 Android 4.0 以及更高版本系统中加入的功能,通过 Wifi P2P 可以在不连接网络的情况下,直接与配对的设备进行数据交换.相对于蓝牙,Wifi P2P 的搜索速度和传输速度更快,传输距离更远 实现的效果如下所示: 客户端.png 服务器端.png 一

随机推荐