如何在 Android 应用程序中录制/播放音频时创建正弦波动画?

问题描述 投票:0回答:3

在iOS应用程序中创建很容易。任何人都可以告诉我在录制和播放音频时创建 Sinwave 动画的简单方法。我尝试了很多方法,但我做不到。有没有第三方框架?

任何人都可以帮帮我吗..

java android avaudioplayer record sine-wave
3个回答
20
投票

使用下面的代码像ios一样获取波形...从录音机获取幅度并传递给 updateAmplitude() 函数以获取语音的变化。

public class WaveFormView extends View {

      private static final float defaultFrequency          = 1.5f;
      private static final float defaultAmplitude          = 1.0f;
      private static final float defaultIdleAmplitude      = 0.01f;
      private static final float defaultNumberOfWaves      = 5.0f;
      private static final float defaultPhaseShift         = -0.15f;
      private static final float defaultDensity            = 5.0f;
      private static final float defaultPrimaryLineWidth   = 3.0f;
      private static final float defaultSecondaryLineWidth = 1.0f;

      private float phase;
      private float amplitude;
      private float frequency;
      private float idleAmplitude;
      private float numberOfWaves;
      private float phaseShift;
      private float density;
      private float primaryWaveLineWidth;
      private float secondaryWaveLineWidth;
      Paint mPaintColor;
      Rect rect;
      boolean isStraightLine = false;

      public WaveFormView(Context context) {
          super(context);
          setUp();
      }

      public WaveFormView(Context context, AttributeSet attrs) {
          super(context, attrs);
          setUp();
      }

      public WaveFormView(Context context, AttributeSet attrs, int defStyleAttr) {
          super(context, attrs, defStyleAttr);
          setUp();
      }

      @TargetApi(Build.VERSION_CODES.LOLLIPOP)
      public WaveFormView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
          super(context, attrs, defStyleAttr, defStyleRes);
          setUp();
      }

      private void setUp() {
          this.frequency = defaultFrequency;

          this.amplitude = defaultAmplitude;
          this.idleAmplitude = defaultIdleAmplitude;

          this.numberOfWaves = defaultNumberOfWaves;
          this.phaseShift = defaultPhaseShift;
          this.density = defaultDensity;

          this.primaryWaveLineWidth = defaultPrimaryLineWidth;
          this.secondaryWaveLineWidth = defaultSecondaryLineWidth;
          mPaintColor = new Paint();
          mPaintColor.setColor(Color.WHITE);
      }

      public void updateAmplitude(float ampli, boolean isSpeaking) {
          this.amplitude = Math.max(ampli, idleAmplitude);
          isStraightLine = isSpeaking;
      }


      @Override
      protected void onDraw(Canvas canvas) {
          rect = new Rect(0,0,canvas.getWidth(),canvas.getWidth());
          canvas.drawColor(Color.BLUE);
          /*canvas.drawRect(rect, mPaintColor);*/
          if(isStraightLine) {
              for (int i = 0; i < numberOfWaves; i++) {
                  mPaintColor.setStrokeWidth(i == 0 ? primaryWaveLineWidth : secondaryWaveLineWidth);
                  float halfHeight = canvas.getHeight() / 2;
                  float width = canvas.getWidth();
                  float mid = canvas.getWidth() / 2;

                  float maxAmplitude = halfHeight - 4.0f;
                  float progress = 1.0f - (float) i / this.numberOfWaves;
                  float normedAmplitude = (1.5f * progress - 0.5f) * this.amplitude;
                  Path path = new Path();

                  float multiplier = Math.min(1.0f, (progress / 3.0f * 2.0f) + (1.0f / 3.0f));

                  for (float x = 0; x < width + density; x += density) {
                      // We use a parable to scale the sinus wave, that has its peak in the middle of the view.
                      float scaling = (float) (-Math.pow(1 / mid * (x - mid), 2) + 1);

                      float y = (float) (scaling * maxAmplitude * normedAmplitude * Math.sin(2 * Math.PI * (x / width) * frequency + phase) + halfHeight);

                      if (x == 0) {
                          path.moveTo(x, y);
                      } else {
                          path.lineTo(x, y);
                      }
                  }
                  mPaintColor.setStyle(Paint.Style.STROKE);
                  mPaintColor.setAntiAlias(true);
                  canvas.drawPath(path, mPaintColor);

              }
          } else {
              canvas.drawLine(5,canvas.getHeight()/2,canvas.getWidth(),canvas.getHeight()/2,mPaintColor );
              canvas.drawLine(0,canvas.getHeight()/2,canvas.getWidth(),canvas.getHeight()/2,mPaintColor);
              canvas.drawLine(-5, canvas.getHeight() / 2, canvas.getWidth(), canvas.getHeight()/2,mPaintColor );
          }
          this.phase += phaseShift;
          invalidate();
      }
}

4
投票

基于@Vennila 的回答。

这是

WaveFormView
的 Kotlin 版本。

class SiriVisualView(context: Context, attrs: AttributeSet) : View(context, attrs) {

    private var phase = 0f
    private var amplitude = 1.5f // wave height
    private var frequency = 1.2f // number of waves
    private var idleAmplitude = 0.05f // default height
    private var numberOfWaves = 8.0f // number of wave lines
    private var phaseShift = -0.1f // wave speed
    private var primaryWaveLineWidth = 2.0f // outer line stroke
    private var secondaryWaveLineWidth = 0.5f // inner line stroke
    private var density = 5f
    var mPaintColor: Paint = Paint()
    var isStraightLine = false

    fun updateViewColor(@ColorInt color: Int) {
        mPaintColor.color = color
    }

    fun updateSpeaking(isSpeaking: Boolean) {
        isStraightLine = isSpeaking
    }

    fun updateAmplitude(ampli: Float) {
        amplitude = Math.max(ampli, idleAmplitude)
    }

    fun updateSpeed(phase: Float) {
        phaseShift = phase
    }

    /** Here you can override default wave values and customize SiriVisualView

        updateNumberOfWaves()
        updatePrimaryLineStroke()
            . . .
    */

    override fun onDraw(canvas: Canvas) {
        if (isStraightLine) {
            var i = 0
            while (i < numberOfWaves) {
                mPaintColor.strokeWidth = if (i == 0) primaryWaveLineWidth else secondaryWaveLineWidth
                val halfHeight = height / 2.toFloat()
                val width = width.toFloat()
                val mid = width / 2.toFloat()
                val maxAmplitude = halfHeight - 4.0f
                val progress = 1.0f - i.toFloat() / numberOfWaves
                val normedAmplitude = (1.5f * progress - 0.5f) * amplitude
                val path = Path()
                val multiplier = Math.min(1.0f, progress / 3.0f * 2.0f + 1.0f / 3.0f)
                var x = 0f
                while (x < width + density) {
                    // We use a parable to scale the sinus wave, that has its peak in the middle of the view.
                    val scaling = (-Math.pow(
                        1 / mid * (x - mid).toDouble(),
                        2.0
                    ) + 1).toFloat()
                    val y = (scaling * maxAmplitude * normedAmplitude * Math.sin(2 * Math.PI * (x / width) * frequency + phase) + halfHeight).toFloat()
                    if (x == 0f) {
                        path.moveTo(x, y)
                    } else {
                        path.lineTo(x, y)
                    }
                    x += density
                }
                mPaintColor.style = Paint.Style.STROKE
                mPaintColor.isAntiAlias = true
                canvas.drawPath(path, mPaintColor)
                i++
            }
        } else {
            for(i in 5 downTo -5 step 5) {
                canvas.drawLine(
                    i.toFloat(),
                    height / 2.toFloat(),
                    width.toFloat(),
                    height / 2.toFloat(),
                    mPaintColor
                )
            }
        }
        phase += phaseShift
        invalidate()
    }
}

将视图添加到您的 XML 文件

<linc.com.visualtest.SiriVisualView
    android:id="@+id/siriView"
    android:layout_width="0dp"
    android:layout_height="200dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toEndOf="parent"/>

更新和自定义 Activity 或 Fragment 内的视图

siriView.apply {
        updateSpeaking(true)
        updateViewColor(Color.WHITE)
        updateAmplitude(0.5f)
        updateSpeed(-0.1f)
    }

这里是 SiriVisualView 的一些屏幕截图


0
投票

我需要类似的东西,但我需要垂直线(音频波形)而不是正弦波。我修改了上面给出的答案来实现它(谢谢@Vennila)。希望对其他人有帮助:)

import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;

public class WaveFormView extends View {

    private static final float defaultFrequency          = 1.5f;
    private static final float defaultAmplitude          = 1.0f;
    private static final float defaultIdleAmplitude      = 0.01f;
    private static final float defaultNumberOfWaves      = 25.0f;
    private static final float defaultPhaseShift         = -0.15f;
    private static final float defaultDensity            = 5.0f;
    private static final float defaultPrimaryLineWidth   = 6.0f;
    private static final float defaultSecondaryLineWidth = 6.0f;

    private float phase;
    private float amplitude;
    private float frequency;
    private float idleAmplitude;
    private float numberOfWaves;
    private float phaseShift;
    private float density;
    private float primaryWaveLineWidth;
    private float secondaryWaveLineWidth;
    Paint mPaintColor;
    Rect rect;
    boolean isStraightLine = false;

    public WaveFormView(Context context) {
        super(context);
        setUp();
    }

    public WaveFormView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setUp();
    }

    public WaveFormView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        setUp();
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public WaveFormView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        setUp();
    }

    private void setUp() {
        this.frequency = defaultFrequency;

        this.amplitude = defaultAmplitude;
        this.idleAmplitude = defaultIdleAmplitude;

        this.numberOfWaves = defaultNumberOfWaves;
        this.phaseShift = defaultPhaseShift;
        this.density = defaultDensity;

        this.primaryWaveLineWidth = defaultPrimaryLineWidth;
        this.secondaryWaveLineWidth = defaultSecondaryLineWidth;
        mPaintColor = new Paint();
        mPaintColor.setColor(Color.WHITE);
    }

    public void updateAmplitude(float ampli, boolean isSpeaking) {
        this.amplitude = Math.max(ampli, idleAmplitude);
        isStraightLine = isSpeaking;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        rect = new Rect(0, 0, canvas.getWidth(), canvas.getHeight());
        canvas.drawColor(getResources().getColor(R.color.dark_grey));

        if (isStraightLine) {
            int numLines = (int) numberOfWaves;
            float lineSpacing = canvas.getWidth() / (numLines + 1);
            float halfHeight = canvas.getHeight() / 2;
            float maxAmplitude = halfHeight - 4.0f;

            for (int i = 0; i < numLines; i++) {
                float lineX = (i + 1) * lineSpacing;
                float normedAmplitude = (1.5f * (1.0f - (float) i / this.numberOfWaves) - 0.5f) * this.amplitude;
                float lineHeight = maxAmplitude * normedAmplitude;

                mPaintColor.setStrokeWidth(i == 0 ? primaryWaveLineWidth : secondaryWaveLineWidth);
                canvas.drawLine(lineX, halfHeight - lineHeight / 2, lineX, halfHeight + lineHeight / 2, mPaintColor);
            }
        } else {
            canvas.drawLine(5, canvas.getHeight() / 2, canvas.getWidth(), canvas.getHeight() / 2, mPaintColor);
            canvas.drawLine(0, canvas.getHeight() / 2, canvas.getWidth(), canvas.getHeight() / 2, mPaintColor);
            canvas.drawLine(-5, canvas.getHeight() / 2, canvas.getWidth(), canvas.getHeight() / 2, mPaintColor);
        }

        this.phase += phaseShift;
        invalidate();
    }
}

© www.soinside.com 2019 - 2024. All rights reserved.