Android高仿iOS Messages声音播放波形效果

文接《Android低仿iOS Messages录音波形效果》。

上一次开发中,因为无法完美实现波形的收敛效果,因此只能算是一个低仿的版本。

声音波形效果对比之下,比较容易实现。

一、目标

实现声音播放的波形效果,为神马笔记增加录音及播放功能做准备。

二、功能分析

截图 描述
在这里插入图片描述 停止时的波形显示为全白色。
在这里插入图片描述 播放过程中,波形有暗变亮,以呈现进度。

三、实现效果

基本上实现了播放的波形效果。
在这里插入图片描述

四、实现过程

1. 创建波形

绘制波形的前提条件是有波形数据。

波形数据的来源有2种。

  1. 从音频文件读取数据,然后转换为波形数据
  2. 录音时进行采样保存

通过MediaRecorder实现录音时,通过getMaxAmplitude()获取最近最大的振幅,通过一段时间的连续调用,最终可以组成一个波形数据。

有了波形数据后,还需要根据控件大小转换为目标数据。

2. Waveform

从波形数据创建Waveform,用来显示完整波形。

sample()Waveform的核心方法,实现转换波形数据的功能。

/**
     *
     */
private static class Waveform {

  ArrayList<Wave> list;
  ArrayList<Wave> recycler;

  TapePlayView parent;

  Waveform(TapePlayView parent) {
    this.parent = parent;

    this.list = new ArrayList<>(100);
    this.recycler = new ArrayList<>(100);
    for (int i = 0; i < 100; i++) {
      recycler.add(new Wave());
    }
  }

  void clear() {
    this.recycler.addAll(list);
    list.clear();
  }

  void sample() {
    this.recycler.addAll(list);
    this.list.clear();

    int count = parent.getWidth() / parent.getWaveWidth();
    if (count == 0) {
      return;
    }

    TapeView.Sampler sampler = parent.sampler;
    if (sampler == null || sampler.size() == 0) {
      return;
    }

    int size = sampler.size();

    if (count == size) { // equals

      for (int i = 0; i < size; i++) {
        int value = sampler.get(i);
        this.addSquare(value);
      }

    } else if (count > size) { // more than

      {
        int length;
        if (count >= 2 * size) { // more than twice
          length = (count - 2 * size) / 2;
        } else {
          length = (count - size) / 2;
        }

        while (length > 0) {
          this.add(0);
          --length;
        }
      }

      if (count >= 2 * size) {
        this.addSquare(sampler.get(0) / 2);
        this.addSquare(sampler.get(0));

        for (int i = 1; i < size; i++) {
          double a = sampler.get(i - 1);
          a *= a;

          double b = sampler.get(i);
          b *= b;

          a = (a + b) / 2;

          this.add(a);
          this.add(b);
        }

      } else {
        for (int i = 0; i < size; i++) {
          int value = sampler.get(i);
          this.addSquare(value);
        }
      }

      {
        while (this.size() < count) {
          this.add(0);
        }
      }

    } else { // less than

      long[] values = new long[count];
      Arrays.fill(values, 0);

      int[] numbers = new int[count];
      Arrays.fill(numbers, 0);

      for (int i = 0; i < size; i++) {

        double value = sampler.get(i);
        value *= value;

        int index = i * count / size;
        values[index] += value;
        numbers[index] += 1;
      }

      for (int i = 0; i < count; i++) {
        if (numbers[i] == 0) {
          this.add(0);
        } else {
          this.add(values[i] / numbers[i]);
        }
      }
    }
  }

  int size() {
    return list.size();
  }

  void draw(Canvas canvas) {
    for (Wave wave : list) {
      wave.draw(canvas);
    }
  }

  void addSquare(int value) {
    this.add(value * value);
  }

  void add(double amplitudeSquare) {
    int position = list.size();
    Wave wave = fetch(position, amplitudeSquare);
    list.add(wave);
  }

  Wave fetch(int position, double amplitudeSquare) {

    Wave wave;

    int index = recycler.size() - 1;
    if (index < 0) {
      wave = new Wave();
    } else {
      wave = recycler.remove(index);
    }

    wave.init(parent, position, amplitudeSquare);
    return wave;
  }
}

3. Wave

Wave用来绘制单个的波形柱状图。

根据播放时间的进度显示不同的效果。

private static class Wave {

  int wave;
  int position;

  TapePlayView parent;

  Wave() {

  }

  void init(TapePlayView parent, int position, double amplitudeSquare) {
    this.parent = parent;

    this.position = position;
    this.wave = parent.transform(amplitudeSquare);
  }

  void draw(Canvas canvas) {
    int width = parent.getWaveWidth();
    int height = parent.getWaveHeight(wave);

    int x = position * width;
    int y = (parent.getHeight() - height) / 2;

    Drawable d;

    if (parent.isRunning()) {
      int progress = parent.getProgress();

      if (x >= progress) { // dark

        d = parent.darkDrawable;
        d.setBounds(x, y, x + parent.sampleSolid, y + height);
        d.draw(canvas);

      } else if ((progress - x) >= parent.sampleSolid) { // bright

        d = parent.drawable;
        d.setBounds(x, y, x + parent.sampleSolid, y + height);
        d.draw(canvas);

      } else {

        int diff = progress - x;

        d = parent.darkDrawable;
        d.setBounds(x, y, x + parent.sampleSolid, y + height);
        d.draw(canvas);

        d = parent.drawable;
        d.setBounds(x, y, x + diff, y + height);
        d.draw(canvas);
      }

    } else {

      d = parent.drawable;
      d.setBounds(x, y, x + parent.sampleSolid, y + height);
      d.draw(canvas);

    }
  }
}

4. 播放声音

使用MediaPlayer来播放声音,通过getDuration()getCurrentPosition()获取总时长和当前进度,从而绘制出播放时的波形。

五、开发过程回顾

从转换波形数据开始,创建波形效果,然后绘制了静态的波形图。

之后结合播放进度,绘制不同状态的波形效果。

难点在于获取波形数据。

六、接下来

组合录音和播放,完整录音编辑器功能。

七、Finally

我应灭度一切众生。
灭度一切众生已。
而无有一众生实灭度者。

猜你喜欢

转载自blog.csdn.net/chuyangchangxi/article/details/91974854