พฤศจิกายน 22, 2017, 10:55:05 am *
ยินดีต้อนรับคุณ, บุคคลทั่วไป กรุณา เข้าสู่ระบบ หรือ ลงทะเบียน
ส่งอีเมล์ยืนยันการใช้งาน?

เข้าสู่ระบบด้วยชื่อผู้ใช้ รหัสผ่าน และระยะเวลาในเซสชั่น
   หน้าแรก   ช่วยเหลือ เข้าสู่ระบบ สมัครสมาชิก  
หน้า: [1]   ลงล่าง
  พิมพ์  
ผู้เขียน หัวข้อ: Microphone Capture for Audio Processing (using AsyncTask synchronized)  (อ่าน 8866 ครั้ง)
0 สมาชิก และ 1 บุคคลทั่วไป กำลังดูหัวข้อนี้
ShadowMan
Administrator
Hero Member
*****
ออฟไลน์ ออฟไลน์

เพศ: ชาย
กระทู้: 8267


ShadowWares


| |
« เมื่อ: ธันวาคม 21, 2012, 05:02:32 pm »

Microphone Capture for Audio Processing (using AsyncTask synchronized)

ตอนนี้จะเอาใจนักประมวลผลสัญญาณเสียง กับ Multitasking โดยเฉพาะ
ปัญหาหนักอกหนักใจในวงจรนี้ไม่มีอะไรมากไปกว่า Thread ต่างๆ แย่งกันใช้ทรัพยากรณ์ ในที่นี้คือ Buffer ที่เก็บข้อมูลเสียงนั่นเอง
ในตัวอย่างที่จะยกให้เห็นต่อไปนี้ เพิ่มความยากขึ้นไปอีกนิดโดยการนำข้อมูลใน Buffer ตัวเดียวกันนี้ไปวาดเป็นรูปสัญญาณออกมาด้วย
เพราะฉนั้นในตัวอย่างนี้จะมี 2 Thread ที่แก่งแย่งให้ทรัพยาการณ์กันอยู่ นั่นคือ Thread ที่ทำการ Capture เสียง และ Thread ที่ทำการวาดรูปสัญญาณ
ในตัวอย่างนี้จะชี้ให้เห็นถึงความง่ายของ Java สำหรับการจัดการเรื่องการแย่งกันใช้ทรัพยากรณ์ ในที่นี้จะยกตัวอย่างสองตัวนั่นคือ "AsyncTask"  และ "synchronized" ซึ่งผู้ที่เล่น Multitasking with UI เลี่ยงไม่ได้

AsyncTask ถูกออกแบบมาให้ทำงานเป็น Background/Worker Task เหมาะกับ Task ที่ทำงานในช่วงระยะเวลาไม่นานมากนัก (น้อยกว่า 1 นาที) เช่น Task ที่ทำการ Download รูปภาพมาแสดงหรือ Download HTML Content มาแสดง เป็นต้น จนอกจากนี้ในระหว่างที่ Task นี้ทำงานอยู่ ยังสามารถที่จะทำการสื่อสารกับ UI Thread เพื่อรายงานผลการกระทำได้อย่างต่อเนื่องอีกด้วย อย่างไรก็ตามเราสามารถบังคับให้ AsyncTask ทำงานในรูปแบบของ Infinite loop ได้ ตามตัวอย่างด้านล่าง
ในกรณีที่ต้องการให้ Application ทำงานแบบ Multitasking ตามรูปแบบที่สมบูรณ์เชิงหลักการและแนวคิด จะต้องใช้ Executor, ThreadPoolExecutor, FutureTask และ Java Concurrency ต่างๆ ซึ่งจะไม่ขอกล่าวถึงในที่นี้

synchronized ใช้ในการป้องกันการเข้าถึง Object จาก Thread ต่างๆ ในเวลาเดียวกัน ซึ่งส่งผลให้การทำงานผิดพลาด ในตัวอย่างนี้ได้แนะนำหลักการใช้งาน แต่ไม่ได้ชี้ให้เห็นถึงผลกระทบเมื่อไม่ได้ทำการ Synchronization เพราะโปรแกรมตัวอย่างด้านล่าง ได้ใช้ความสามารถของ AsyncTask และ Handler เข้ามาจัดการ รายละเอียดของ synchronized ผมจะพูดถึงภายหลังควบคู่ไปกับเรื่อง Multitasking ของจริงในตอนต่อๆ ไป


ในตอนนี้ถือว่าเป็นเรื่องที่ยากในระดับท้ายๆ ของการเขียนโปรแกรม โดยเฉพาะอย่างยิ่งบน Mobile Device เพราะการที่จะควบคุม Sampling Rate, Frame Rate ให้ได้ดังใจนั้น เป็นเรื่องที่ยาก โดยเฉพาะอย่างยิ่งเมื่อต้องการ Frame Rate สูงๆ แล้ะต้องการ Update Display ด้วย Frame Rate ที่เท่ากัน จะต้องได้มาจากการรู้เท่าทันเวลาในส่วนต่างๆ ก่อนเขียนโปรแกรม ในตอนก่อนหน้าผมได้ชี้แจงให้เห็นแล้วว่าเราจะทำการตรวจสอบความสามารถของการ Update Display ของเครืองที่เราใช้อยู่ได้อย่างไร สำหรับเรื่องเวลาในการ Capture เสียงนั้นคำนาวนได้ตรงไปตรงมา เพราะเรารู้ Frame Size และ Sampling Rate อยู่แล้ว นั่นคือจับเวลาในการ Capture ใน Frame นั้น มาบวกกับเวลาในการวาดออกจอ +/- ค่าชดเชยให้กับ Thread อื่นๆ ในระบบ ก็จะได้เวลาออมานั่นเอง

ดูผลลัพพธ์กันก่อน:



*** โปรแกรมสองตัวที่เปิดนั้น เปิดมาทดสอบว่า app ของเราจะไม่ไปกินทรัพยากรณ์ของ OS จนหมด จนทำให้ app อื่นทำงานไม่ได้ ในที่นี่ app ของเราถูกกำหนด Priority สูงกว่า app ทั่วไปก็จริง แต่ไม่ได้หมายความว่าจะส่งผลกระทบกับ app อื่นๆ นั่นคือทุกอย่างยังคงทำงานได้อย่างลื่นไหล ***

ต่อไปดู code ในส่วนของ Main Class
Code: (java)
package com.shadowwares.santi.audioprocessing;

import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

public class MainActivity extends Activity {
   private boolean processing;
   private Plot plot;
   public Handler handler;
   private AudioRecord recorder = null;
   private final int sampleRate = 8000;
   private final int bytesPerSample = 2;
   private final int samplesPerFrame = 512;
   private final int bufferSizeInByte = samplesPerFrame * bytesPerSample;
   private short shortBuffer[] = new short[samplesPerFrame];
   private short displayBuffer[] = new short[samplesPerFrame];

   @Override
   protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      Log.i("Santi", "length=" + shortBuffer.length);
   }

   @Override
   protected void onStart() {
      setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
      Log.i("Santi", "onStart");
      super.onStart();
      plot = new Plot(MainActivity.this);
      Worker task = new Worker();
      task.execute();

      handler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
            super.handleMessage(msg);
            plot.Update(MainActivity.this, displayBuffer);
         }
      };
   }

   @Override
   protected void onStop() {
      Log.i("Santi", "onStop");
      super.onStop();
      processing = false;
   }

   private class Worker extends AsyncTask<Void, Void, Void> {
      @Override
      protected void onPostExecute(Void result) {
         super.onPostExecute(result);
         Log.i("Santi", "onPostExecute");
         processing = false;
         recorder.stop();
         recorder.release();
      }

      @Override
      protected void onPreExecute() {
         super.onPreExecute();
         Log.i("Santi", "onPreExecute");
         recorder = new AudioRecord(MediaRecorder.AudioSource.MIC,
               sampleRate, AudioFormat.CHANNEL_IN_MONO,
               AudioFormat.ENCODING_PCM_16BIT, bufferSizeInByte);
      }

      @Override
      protected Void doInBackground(Void... params) {
         android.os.Process
               .setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
         processing = true;
         recorder.startRecording();
         while (processing) {
            recorder.read(shortBuffer, 0, samplesPerFrame);
            synchronized (shortBuffer) {
               displayBuffer = shortBuffer.clone();
            }
            handler.sendEmptyMessage(0);
         }
         return null;
      }
   }
}


Code ในส่วนของการ Plot (ไม่มีการ Optimization ใดๆ)
Code: (java)
package com.shadowwares.santi.audioprocessing;

import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.View;

public class Plot extends View {
   protected Paint paint;
   private float[] data;

   private long stopTime = 0;
   private long startTime = 0;

   public Plot(Context context) {
      super(context);
      paint = new Paint();
      startTime = System.nanoTime();
   }

   public void Update(Context context, short[] buffer) {
      data = new float[buffer.length];
      for (int ii = 0; ii < buffer.length; ii++) {
         data[ii] = (float) buffer[ii] / 32f;
      }
      invalidate();
      ((Activity) context).setContentView(this);
   }

   @Override
   protected void onDraw(Canvas canvas) {
      super.onDraw(canvas);

      paint.setStyle(Paint.Style.FILL);
      paint.setColor(0xff2244aa);
      canvas.drawPaint(paint);

      paint.setAntiAlias(true);
      paint.setColor(Color.MAGENTA);
      paint.setStrokeWidth(2);

      int w = canvas.getWidth();
      int h = canvas.getHeight();

      for (int ii = 0; ii < data.length; ii++) {
         data[ii] = data[ii] + h / 2;
      }

      float k = w / (float) data.length;
      for (int ii = 0; ii < data.length - 1; ii++) {
         int jj = (int) ((float) ii * k);
         canvas.drawLine((float) jj, (float) data[ii], (float) jj + 1,
               (float) data[ii + 1], paint);
      }
      stopTime = System.nanoTime();

      paint.setColor(Color.GREEN);

      float time = (float) (stopTime - startTime) / 1000000000f;
      paint.setTextSize(40);
      canvas.drawText(String.format("Avg Frame Rate=%4.3f Hz", 1f / time),
            10, 40, paint);
      canvas.drawText(String.format("Sampling Rate=%4.3f Hz", 8000f), 10, 80,
            paint);
      canvas.drawText(String.format("Frame Size=%4.3f", 512f), 10, 120, paint);

      startTime = System.nanoTime();

   }
}


การใช้ AudioRecord API ไม่เหมาะสำหรับงานแบบ Real-Time ลักษณะนี้ เพราะ ณ เวลานี้ทางผู้พัฒนา OS การันตีเวลาที่ 10 mS ซึ่งถือว่าเยอะพอสมควรเมื่อเทียบกับความเร็วของ CPU แต่สำหรับ Application ที่เป็น Recorder วิธีนี้ถือได้ว่าสะดวกมากพอสมควร

รายละเอียดขอไม่กล่าวถึงนะครับ สงสัยส่วนไหนถามต่อด้านล่างได้เลย
ไว้ว่างๆ ผมจะมาเทียบความเร็วระหว่าง Java-SDK กับ C-NDK Native ให้ดู จะได้ทราบเหตุผลที่ชัดเจนว่าทำไม google ต้องพัฒนา NDK สำหรับ Dalvik-JNI



การบ้าน
ให้นำสัญญาณเสียงที่ได้ในแต่ละเฟรม ไปหาค่าองค์ประกอบความถี่ และแสดงผลออกมาในรูปของ Power spectrum แบบ Real-time
บันทึกการเข้า

By SDW: Do No Wrong Is Do Nothing
          If you want to increase your success rate, double your failure rate
kulaza
Newbie
*
ออฟไลน์ ออฟไลน์

กระทู้: 3


| |
« ตอบ #1 เมื่อ: กุมภาพันธ์ 18, 2014, 01:14:26 am »

ถ้าอยากให้สัญญาณจากไมโครโฟนที่แสดงออกมาอยู่ในกรอบเหมือนในรูปที่แนบ ช่วยแนะนำวิธีให้หน่อยได้ไหมครับ

บันทึกการเข้า
ShadowMan
Administrator
Hero Member
*****
ออฟไลน์ ออฟไลน์

เพศ: ชาย
กระทู้: 8267


ShadowWares


| |
« ตอบ #2 เมื่อ: กุมภาพันธ์ 18, 2014, 07:44:03 am »

อะไรคือจุดสนใจในรูปนั้น spectrogram หรือเปล่า? ถ้าใช่นำสัญญาณไปเปลี่ยนเป็น frequency domain ด้วย FFT แล้ววาดออกมาโดยวิธีการเดียวกันครับ
บันทึกการเข้า

By SDW: Do No Wrong Is Do Nothing
          If you want to increase your success rate, double your failure rate
kulaza
Newbie
*
ออฟไลน์ ออฟไลน์

กระทู้: 3


| |
« ตอบ #3 เมื่อ: กุมภาพันธ์ 18, 2014, 10:21:26 pm »

สนใจแค่ใน time domain อะครับ แค่อยากให้กราฟที่ออกมาอยู่ในรูปแบบรูปข้างบน ส่วนข้างล่างอยากให้เป็นปุ่มเอาไว้สำหรับบันทึกเสียงอะครับ

แต่ตอนนี้ผมติดตรงที่ว่า ภาพที่มันรันออกมามันเต็มจอแล้วมองไม่เห็นปุ่มที่ผมเขียนลงไปอะครับ (เหมือนโดนบังอยู่)

ไม่ทราบว่าต้องทำอย่างไรครับ

อยากได้แบบรูปนี้อะครับ
บันทึกการเข้า
ShadowMan
Administrator
Hero Member
*****
ออฟไลน์ ออฟไลน์

เพศ: ชาย
กระทู้: 8267


ShadowWares


| |
« ตอบ #4 เมื่อ: กุมภาพันธ์ 19, 2014, 08:40:11 am »

ถ้าเป็นอบบนี้ต้องศึกษาพื้นฐาน GUI ก่อนแล้วครับ
หลักการง่ายๆคือ อย่าไปวาดทับตำแหน่งที่ปุ่มอยู่ โดยการแบ่งแยกให้ชัดเจนว่าจะวาดเส้นตรงไหน จะวางปุ่มตรงไหน
บันทึกการเข้า

By SDW: Do No Wrong Is Do Nothing
          If you want to increase your success rate, double your failure rate
kulaza
Newbie
*
ออฟไลน์ ออฟไลน์

กระทู้: 3


| |
« ตอบ #5 เมื่อ: กุมภาพันธ์ 24, 2014, 02:29:58 pm »

ขอบคุณครับ
บันทึกการเข้า
Yuki
Newbie
*
ออฟไลน์ ออฟไลน์

กระทู้: 1


| |
« ตอบ #6 เมื่อ: พฤษภาคม 05, 2014, 03:45:03 pm »

รบกวนพี่สันติอธิบายตรงส่วนของการ plot หน่อยได้ไหมครับ ว่าตัวแปรแต่ละตัว รับมาจากตัวแปรไหนในหน้า Main

และพอมีวิธีรับค่าจากตัวแปร จาก หน้า Main แล้วนำมา plot graph ในหน้า plot ด้วยวิธีอื่นไหมครับ ขอบคุณครับ
บันทึกการเข้า
ShadowMan
Administrator
Hero Member
*****
ออฟไลน์ ออฟไลน์

เพศ: ชาย
กระทู้: 8267


ShadowWares


| |
« ตอบ #7 เมื่อ: พฤษภาคม 08, 2014, 08:46:15 pm »

อ้างถึง
รบกวนพี่สันติอธิบายตรงส่วนของการ plot หน่อยได้ไหมครับ ว่าตัวแปรแต่ละตัว รับมาจากตัวแปรไหนในหน้า Main
โปรแกรมที่เห็นไม่ได้ยาวมากมายนัก คุณต้องฝึกคิด ไล่ล่าหาคำตอบด้วยตัวเอง ถ้าไม่ได้ แสดงว่าพื้นฐานยังไม่มากพอ ควรอ่านพื้นฐานให้มากกว่านี้
หรือถ้าคิดหาคำตอบด้วยตัวเองจนสุดความสามารถแล้ว ยังไม่ได้จริงๆ ให้เฉพาะเจอจงมาว่า บรรทัดไหน ตัวแปรตัวไหนที่คุณตีไม่แตก


อ้างถึง
และพอมีวิธีรับค่าจากตัวแปร จาก หน้า Main แล้วนำมา plot graph ในหน้า plot ด้วยวิธีอื่นไหมครับ ขอบคุณครับ
อะไรคือความหมายของคำว่า "หน้า Main" ?
บันทึกการเข้า

By SDW: Do No Wrong Is Do Nothing
          If you want to increase your success rate, double your failure rate
chirawat
Jr. Member
**
ออฟไลน์ ออฟไลน์

กระทู้: 54


| |
« ตอบ #8 เมื่อ: พฤษภาคม 29, 2014, 10:49:55 pm »

ขอบคุณมากครับ ผมต้องการวาดกราฟสองมิติ ผมไปขลุกอยู่กับ OpenGL ES มากไป เพิ่งรู้ว่า Android เค้าวาดรูปกันอย่างนี้ ต้องขอบคุณโค้ดของคุณสันติทำให้ผมเริ่มเห็นภาพมากขึ้นแล้วครับ
บันทึกการเข้า
หน้า: [1]   ขึ้นบน
  พิมพ์  
 
กระโดดไป: