마스터Q&A 안드로이드는 안드로이드 개발자들의 질문과 답변을 위한 지식 커뮤니티 사이트입니다. 안드로이드펍에서 운영하고 있습니다. [사용법, 운영진]

안드로이드 스튜디오 소리녹음 관련 질문드립니다!

0 추천

안녕하세요! 이번에 안드로이드 스튜디오를 통해 소리관련 프로젝트를 진행하고있습니다!

현재까지 진행상황은 돌아다니는 소스코드를 가져다가 소리가 들어오면 주파수를 비트맵에 표현해주는것까지

완성한 상황입니다! 쉽게 설명드려서 아래 그림과 같습니다!

이런식으로 소리가 들어오게 되면 주파수가 비트맵에 그려지는 상황인대 제가 구현하고 싶은내용은

약 2초간 녹음을 통해 녹음된 이미지를 비트맵에 그려주고,  한계주파수를(Threshold frequency)

비트맵 아래에 적어주고 싶습니다. 어떻게 하면 시간을 정해서 녹음을 하고 그에따른 주파수를 분석해

값을 출력할수 있는지 조언 부탁드리겠습니다! 아래는 메인엑티비티 코드입니다!

package com.example.samsung.audiofinal;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;

import ca.uol.aig.fftpack.RealDoubleFFT;


//FFT(Fast Fourier Transform) DFT 알고리즘 : 데이터를 시간 기준(time base)에서 주파수 기준(frequency base)으로 바꾸는데 사용.
//AudioProcessingActivity클래스는 Activity를 상속하며, OnClickListener를 구현하겠다라는 의미
public class AudioProcessingActivity extends Activity implements OnClickListener {
	// AudioRecord 객체에서 주파수는 8kHz, 오디오 채널은 하나, 샘플은 16비트를 사용
	int mcompare=0;
	int frequency = 8000;
	int channelConfiguration = AudioFormat.CHANNEL_IN_MONO;
	int audioEncoding = AudioFormat.ENCODING_PCM_16BIT;
	// FFT 객체 transformer 생성
	// 이 FFT 객체를 통해 AudioRecord 객체에서 한 번에 256가지 샘플을다룬다 사용하는 샘플의 수는 FFT 객체를 통해
	// 샘플들을 실행하고 가져올 주파수의 수와 일치한다. 다른 크기를 마음대로 지정해도 되지만, 메모리와 성능 측면을 반드시 고려해야함
	// 적용될 수학적 계산이 프로세서의 성능과 밀접한 관계를 보이기 때문이다.
	// 버튼 객체 생성 및 boolean 논리형을 통해 참과 거짓을 판단할 수 있도록 함
	private RealDoubleFFT transformer;
	int blockSize = 256;
	Button startStopButton;
	boolean started = false;

	// RecordAudio는 여기에서 정의되는 내부 클래스이며 아래는 RecordAudio의 객체생성
	RecordAudio recordTask;

	// Bitmap 이미지를 표시하기 위해 ImageView를 사용한다. 이 이미지는 현재 오디오 스트림에서 주파수들의 레벨을 나타낸다.
	// 이 레벨들을 그리려면 Bitmap에서 구성한 Canvas 객체와 Paint객체가 필요하다.
	ImageView imageView;
	Bitmap bitmap;
	Canvas canvas;
	Paint paint;

	@Override
	// 메모리 부족등으로 다시 복원 될 때, savedInstanceState정보를 가지고 자동으로 복원해줌
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main); // activity_main.xml의 정의된 뷰들을 메모리 상에 객체화한다

		startStopButton = (Button) findViewById(R.id.StartStopButton);
		startStopButton.setOnClickListener(this);

		// RealDoubleFFT 클래스 컨스트럭터는 한번에 처리할 샘플들의 수를 받는다.
		// 그리고 출력될 주파수 범위들의 수를 나타낸다. 위에서 설정한 256개의 샘플을 RealDobleFFT클래스로 보내준다
		transformer = new RealDoubleFFT(blockSize);

		// ImageView 및 관련 객체 설정 부분
		// 이미지뷰를 참조하고, 비트맵을 생성한다 비트맵의 첫번째 두번째 인수가 가로,세로의 크기이며
		// ARGB_8888가 나타내는 의미는 각 픽셀은 4바이트에 저장된다이다
		imageView = (ImageView) findViewById(R.id.ImageView01);
		bitmap = Bitmap.createBitmap((int)320, (int)200,
				Bitmap.Config.ARGB_8888);
		// 비트맵 이미지를 불러와 캔버스에 그림을 그림
		canvas = new Canvas(bitmap);
		paint = new Paint();
		paint.setColor(Color.YELLOW);
		// 비트맵 객체를 이용하여 이미지뷰를 보여줌
		imageView.setImageBitmap(bitmap);
	}

	// 이 액티비티의 작업들은 대부분 RecordAudio라는 클래스에서 진행된다. 이 클래스는 AsyncTask를 확장한다.
	// AsyncTask를 사용하면 사용자 인터페이스를 멍하니 있게 하는 메소드들을 별도의 스레드로 실행한다.
	// 여기서 AsyncTask란, 하나의 클래스에서 UI 작업을 쉽게 할수 있도록 도와주는 클래스
	// 또한 UI란 인터페이스, 즉 정보기기나 소프트웨어의 화면 등 사람과 접하는 면을 설계하는 일이다
	// 안드로이드에서의 일처리는 메인스레드(UI 스레드)가 담당하는대, 그래서 메인스레드를 UI스레드라고도 부른다.
	// 이러한 일들을 하기위해 AysncTask라는 객체를 만들었고 쉽게 구현 할 수 있도록 메서드를 제공한다.(메서드는 함수)
	// 아래 AsyncTask<1, 2, 3>은 각각 아래를 뜻한다
	// 1 = dolnBackground 메서드에 선언하는 가변인수 매개변수의 타입
	// 2 = onProgressUpdate 메서드에 선언하는 가변인수 매개변수의 타입
	// 3 = onPostExecute 메서드에 선언하는 매개변수의 타입
	private class RecordAudio extends AsyncTask<Void, double[], Void> {
		@Override
		protected Void doInBackground(Void... params) {
			try {
				// AudioRecord를 설정하고 사용한다.
				int bufferSize = AudioRecord.getMinBufferSize(frequency, channelConfiguration, audioEncoding);

				AudioRecord audioRecord = new AudioRecord( MediaRecorder.AudioSource.MIC, frequency,
															channelConfiguration, audioEncoding, bufferSize);

				// short로 이뤄진 배열인 buffer는 원시 PCM 샘플을 AudioRecord 객체에서 받는다.
				// double로 이뤄진 배열인 toTransform은 같은 데이터를 담지만 double 타입인데, FFT
				// 클래스에서는 double타입이 필요해서이다.
				short[] buffer = new short[blockSize]; //blockSize = 256
				double[] toTransform = new double[blockSize]; //blockSize = 256

				audioRecord.startRecording();

				while (started) {
					int bufferReadResult = audioRecord.read(buffer, 0, blockSize); //blockSize = 256
Log.i("bufferReadResult", Integer.toString(bufferReadResult));
					// AudioRecord 객체에서 데이터를 읽은 다음에는 short 타입의 변수들을 double 타입으로
					// 바꾸는 루프를 처리한다.
					// 직접 타입 변환(casting)으로 이 작업을 처리할 수 없다. 값들이 전체 범위가 아니라 -1.0에서
					// 1.0 사이라서 그렇다
					// short를 32,767(Short.MAX_VALUE) 으로 나누면 double로 타입이 바뀌는데,
					// 이 값이 short의 최대값이기 때문이다.
					for (int i = 0; i < blockSize && i < bufferReadResult; i++) {
						toTransform[i] = (double) buffer[i] / Short.MAX_VALUE; // 부호 있는 16비트
Log.i("buffer", Double.toString(buffer[i]));
Log.i("Short.MAX_VALUE", Short.toString(Short.MAX_VALUE));
Log.i("toTransform", Double.toString(toTransform[i]));
					}

					// 이제 double값들의 배열을 FFT 객체로 넘겨준다. FFT 객체는 이 배열을 재사용하여 출력 값을 담는다
					// 포함된 데이터는 시간 도메인이 아니라
					// 주파수 도메인에 존재한다. 이 말은 배열의 첫 번째 요소가 시간상으로 첫 번째 샘플이 아니라는 얘기다.
					// 배열의 첫 번째 요소는 첫 번째 주파수 집합의 레벨을 나타낸다.

					// 256가지 값(범위)을 사용하고 있고 샘플 비율이 8,000 이므로 배열의 각 요소가 대략
					// 15.625Hz를 담당하게 된다. 15.625라는 숫자는 샘플 비율을 반으로 나누고(캡쳐할 수 있는
					// 최대 주파수는 샘플 비율의 반이다. <- 누가 그랬는데...), 다시 256으로 나누어 나온 것이다.
					// 따라서 배열의 첫 번째 요소로 나타난 데이터는 영(0)과 15.625Hz 사이에
					// 해당하는 오디오 레벨을 의미한다.

					transformer.ft(toTransform);
					// publishProgress를 호출하면 onProgressUpdate가 호출된다.
					publishProgress(toTransform);
				}

				audioRecord.stop();
			} catch (Throwable t) {
				Log.e("AudioRecord", "Recording Failed");
			}

			return null;
		}

		// onProgressUpdate는 우리 엑티비티의 메인 스레드로 실행된다. 따라서 아무런 문제를 일으키지 않고 사용자
		// 인터페이스와 상호작용할 수 있다.
		// 이번 구현에서는 onProgressUpdate가 FFT 객체를 통해 실행된 다음 데이터를 넘겨준다. 이 메소드는 최대
		// 100픽셀의 높이로 일련의 세로선으로
		// 화면에 데이터를 그린다. 각 세로선은 배열의 요소 하나씩을 나타내므로 범위는 15.625Hz다. 첫 번째 행은 범위가 0에서
		// 15.625Hz인 주파수를 나타내고,
		// 마지막 행은 3,984.375에서 4,000Hz인 주파수를 나타낸다.

		@Override
		protected void onProgressUpdate(double[]... toTransform) {
			canvas.drawColor(Color.BLACK);

			for (int i = 0; i < toTransform[0].length; i++) {
				int x = i;
				int downy = (int) (100 - (toTransform[0][i] * 10));
				int upy = 100;
				int test1 = (int) (toTransform[0][i] * 100);

				if(test1 > 2000){
					Log.e("주파수 : ",test1+"HZ");
				}

				if(test1>mcompare)
					mcompare=test1;

				canvas.drawLine(x, downy, x, upy, paint);
			}
			imageView.invalidate();
		}
	}

	@Override
	public void onClick(View arg0) {
		if (started) {
			started = false;
			startStopButton.setText("Start");
			recordTask.cancel(true);
		} else {
			started = true;
			startStopButton.setText("Stop");
			recordTask = new RecordAudio();
			recordTask.execute();
		}
	}
}

 

익명사용자 님이 2017년 3월 21일 질문
녹음정지를 asynctask 에 cancel로 하시는거같은데
start클릭이벤트후 2초후에 핸들러로 메세지보내서 정지시키면 될거같은데여
주파수는 test1값인거같구  녹음 정지시키면서 test1값을 textView하나 만들어서 setText해주세여
답변 정말 감사합니다!ㅠㅠ 방법은 어떻게 하는지 이해가 가는데,
핸들러에 대한 개념도 아직 부족하고 전반적으로 많이 부족하내요 ㅠㅠ
혹시 실례가 안된다면, 자그마한 예제코드라도 적어주시면 감사하겠습니다!

1개의 답변

0 추천
https://github.com/google/ringdroid 코드를 참조하세요.
익명사용자 님이 2017년 3월 21일 답변
감사합니다! 주파수 부분 많은도움이 될것같습니다!
...