2010-02-15 37 views
21

Ứng dụng Java Android của tôi cần ghi lại dữ liệu âm thanh vào RAM và xử lý nó. Đây là lý do tại sao tôi sử dụng lớp "AudioRecord" chứ không phải "MediaRecorder" (chỉ ghi vào tệp).Android: AudioRecord Class Sự cố: Gọi lại không bao giờ được gọi là

Cho đến bây giờ, tôi đã sử dụng cuộc gọi vòng lặp bận với "read()" cho dữ liệu âm thanh. điều này đã được làm việc cho đến nay, nhưng nó peggs CPU quá nhiều. Giữa hai cuộc thăm dò ý kiến, tôi đặt chỉ vào giấc ngủ để tránh sử dụng CPU 100%. Tuy nhiên, đây không thực sự là giải pháp sạch, vì thời gian ngủ là không được đảm bảo và bạn phải trừ thời gian bảo mật để không bị mất âm thanh đoạn trích. Đây không phải là CPU tối ưu. Tôi cần càng nhiều chu kỳ CPU miễn phí càng tốt cho một luồng chạy song song.

Bây giờ tôi đã triển khai bản ghi bằng cách sử dụng "OnRecordPositionUpdateListener". Điều này có vẻ rất hứa hẹn và đúng cách để làm điều đó theo SDK Tài liệu. Mọi thứ dường như hoạt động (mở thiết bị âm thanh, đọc() ing dữ liệu, v.v.) nhưng Trình nghe không bao giờ được gọi.

Có ai biết tại sao không?

Thông tin: Tôi đang làm việc với một Thiết bị thực, không phải trong Trình giả lập. Việc ghi âm bằng cách sử dụng một Busy Loop cơ bản hoạt động (tuy nhiên không satifiying). Chỉ có Trình nghe gọi lại không bao giờ được gọi.

Dưới đây là một đoạn trích từ sourcecode của tôi:

public class myApplication extends Activity { 

    /* audio recording */ 
    private static final int AUDIO_SAMPLE_FREQ = 16000; 
    private static final int AUDIO_BUFFER_BYTESIZE = AUDIO_SAMPLE_FREQ * 2 * 3; // = 3000ms 
    private static final int AUDIO_BUFFER_SAMPLEREAD_SIZE = AUDIO_SAMPLE_FREQ/10 * 2; // = 200ms 

    private short[] mAudioBuffer = null; // audio buffer 
    private int mSamplesRead; // how many samples are recently read 
    private AudioRecord mAudioRecorder; // Audio Recorder 

    ... 

    private OnRecordPositionUpdateListener mRecordListener = new OnRecordPositionUpdateListener() { 

    public void onPeriodicNotification(AudioRecord recorder) { 
     mSamplesRead = recorder.read(mAudioBuffer, 0, AUDIO_BUFFER_SAMPLEREAD_SIZE); 
     if (mSamplesRead > 0) { 

     // do something here... 

     } 
    } 

    public void onMarkerReached(AudioRecord recorder) { 
     Error("What? Hu!? Where am I?"); 
    } 
    }; 

    ... 

    public void onCreate(Bundle savedInstanceState) { 

    try { 
     mAudioRecorder = new AudioRecord(
      android.media.MediaRecorder.AudioSource.MIC, 
      AUDIO_SAMPLE_FREQ, 
      AudioFormat.CHANNEL_CONFIGURATION_MONO, 
      AudioFormat.ENCODING_PCM_16BIT, 
      AUDIO_BUFFER_BYTESIZE); 
    } catch (Exception e) { 
     Error("Unable to init audio recording!"); 
    } 

    mAudioBuffer = new short[AUDIO_BUFFER_SAMPLEREAD_SIZE]; 
    mAudioRecorder.setPositionNotificationPeriod(AUDIO_BUFFER_SAMPLEREAD_SIZE); 
    mAudioRecorder.setRecordPositionUpdateListener(mRecordListener); 
    mAudioRecorder.startRecording(); 

    /* test if I can read anything at all... (and yes, this here works!) */ 
    mSamplesRead = mAudioRecorder.read(mAudioBuffer, 0, AUDIO_BUFFER_SAMPLEREAD_SIZE); 

    } 
} 

Trả lời

13

Tôi tin rằng vấn đề là bạn vẫn cần phải thực hiện vòng lặp đọc. Nếu bạn thiết lập gọi lại, chúng sẽ kích hoạt khi bạn đã đọc số khung mà bạn chỉ định cho cuộc gọi lại. Nhưng bạn vẫn cần phải đọc. Tôi đã thử điều này và callbacks được gọi là tốt. Thiết lập một điểm đánh dấu gây ra một cuộc gọi lại khi số lượng khung đó đã được đọc kể từ khi bắt đầu ghi. Nói cách khác, bạn có thể thiết lập điểm đánh dấu trong tương lai, sau nhiều lần đọc của bạn, và nó sẽ cháy sau đó. Bạn có thể đặt khoảng thời gian cho một số khung hình lớn hơn và cuộc gọi lại đó sẽ kích hoạt mỗi khi số khung hình đã được đọc. Tôi nghĩ rằng họ làm điều này để bạn có thể thực hiện xử lý cấp thấp của dữ liệu thô trong một vòng lặp chặt chẽ, sau đó thường xuyên gọi lại của bạn có thể thực hiện xử lý cấp tóm tắt. Bạn có thể sử dụng điểm đánh dấu để dễ dàng quyết định thời điểm dừng ghi (thay vì đếm trong vòng đọc).

+0

Cảm ơn bạn - bạn đã giải thích rõ điều này. – chaimp

+1

câu trả lời của bạn rất hữu ích. Tôi đánh giá cao nếu bạn có thể khai sáng cho tôi cách đọc đúng luồng đầu vào trực tiếp từ micrô. Một vòng lặp liên tục trong khi có vẻ không thích hợp vì nó sẽ chặn toàn bộ quá trình, đúng không? Tôi nghĩ rằng các callbacks đã có như một giải pháp, nhưng đánh giá bởi câu trả lời của bạn, họ không. Cảm ơn bạn. – Tom

+0

Vòng lặp liên tục để đọc dữ liệu âm thanh phải nằm trong một chuỗi riêng biệt. Bạn chắc chắn không thể sử dụng chủ đề chính để đọc âm thanh. Các cuộc gọi lại sẽ kích hoạt khi thu thập một lượng dữ liệu âm thanh phù hợp. Bạn có thể đặt logic xử lý vào vòng lặp đọc hoặc bạn có thể sử dụng các cuộc gọi lại để kích hoạt xử lý trên bộ đệm mà bạn vừa đọc.Khối đọc nhưng chỉ trong chuỗi đó, vì vậy bạn sẽ không sao. –

6

Đây là mã của tôi được sử dụng để tìm tiếng ồn trung bình. Lưu ý rằng nó dựa trên thông báo người nghe để nó sẽ tiết kiệm pin thiết bị. Nó chắc chắn dựa trên các ví dụ trên. Những ví dụ đó tiết kiệm rất nhiều thời gian cho tôi, cảm ơn.

private AudioRecord recorder; 
private boolean recorderStarted; 
private Thread recordingThread; 
private int bufferSize = 800; 
private short[][] buffers = new short[256][bufferSize]; 
private int[] averages = new int[256]; 
private int lastBuffer = 0; 

protected void startListenToMicrophone() { 
    if (!recorderStarted) { 

     recordingThread = new Thread() { 
      @Override 
      public void run() { 
       int minBufferSize = AudioRecord.getMinBufferSize(8000, AudioFormat.CHANNEL_CONFIGURATION_MONO, 
         AudioFormat.ENCODING_PCM_16BIT); 
       recorder = new AudioRecord(AudioSource.MIC, 8000, AudioFormat.CHANNEL_CONFIGURATION_MONO, 
         AudioFormat.ENCODING_PCM_16BIT, minBufferSize * 10); 
       recorder.setPositionNotificationPeriod(bufferSize); 
       recorder.setRecordPositionUpdateListener(new OnRecordPositionUpdateListener() { 
        @Override 
        public void onPeriodicNotification(AudioRecord recorder) { 
         short[] buffer = buffers[++lastBuffer % buffers.length]; 
         recorder.read(buffer, 0, bufferSize); 
         long sum = 0; 
         for (int i = 0; i < bufferSize; ++i) { 
          sum += Math.abs(buffer[i]); 
         } 
         averages[lastBuffer % buffers.length] = (int) (sum/bufferSize); 
         lastBuffer = lastBuffer % buffers.length; 
        } 

        @Override 
        public void onMarkerReached(AudioRecord recorder) { 
        } 
       }); 
       recorder.startRecording(); 
       short[] buffer = buffers[lastBuffer % buffers.length]; 
       recorder.read(buffer, 0, bufferSize); 
       while (true) { 
        if (isInterrupted()) { 
         recorder.stop(); 
         recorder.release(); 
         break; 
        } 
       } 
      } 
     }; 
     recordingThread.start(); 

     recorderStarted = true; 
    } 
} 

private void stopListenToMicrophone() { 
    if (recorderStarted) { 
     if (recordingThread != null && recordingThread.isAlive() && !recordingThread.isInterrupted()) { 
      recordingThread.interrupt(); 
     } 
     recorderStarted = false; 
    } 
} 
+1

Ví dụ này dường như quay CPU trong quá trình ghi. Vòng lặp while (true) sẽ được thực hiện liên tục ... – mmigdol

+0

Hi @soul, tại sao bạn đặt periodInFrames của 'setPositionNotificationPeriod' giống như kích thước bộ đệm. –

0

Đối với những người đang ghi âm qua Dịch vụ cố ý, họ cũng có thể gặp phải sự cố gọi lại này. Tôi đã làm việc về vấn đề này gần đây và tôi đã đưa ra một giải pháp đơn giản, nơi bạn gọi phương pháp ghi âm của bạn trong một chủ đề riêng biệt. Điều này nên gọi phương pháp onPeriodicNotification trong khi ghi âm mà không có bất kỳ vấn đề.

Something như thế này:

public class TalkService extends IntentService { 
... 
@Override 
protected void onHandleIntent(Intent intent) { 
Context context = getApplicationContext(); 
tRecord = new Thread(new recordAudio()); 
tRecord.start(); 
... 
while (tRecord.isAlive()) { 
    if (getIsDone()) { 
     if (aRecorder.getRecordingState() == AudioRecord.RECORDSTATE_STOPPED) { 
      socketConnection(context, host, port); 
     } 
    } 
} } 
... 
class recordAudio implements Runnable { 

     public void run() { 
      try { 
       OutputStream osFile = new FileOutputStream(file); 
       BufferedOutputStream bosFile = new BufferedOutputStream(osFile); 
       DataOutputStream dosFile = new DataOutputStream(bosFile); 

       aRecorder = new AudioRecord(MediaRecorder.AudioSource.MIC, 
         sampleRate, channelInMode, encodingMode, bufferSize); 

       data = new short[bufferSize]; 

       aRecorder.setPositionNotificationPeriod(sampleRate); 
       aRecorder 
         .setRecordPositionUpdateListener(new AudioRecord.OnRecordPositionUpdateListener() { 
          int count = 1; 

          @Override 
          public void onPeriodicNotification(
            AudioRecord recorder) { 
           Log.e(WiFiDirect.TAG, "Period notf: " + count++); 

           if (getRecording() == false) { 
            aRecorder.stop(); 
            aRecorder.release(); 
            setIsDone(true); 
            Log.d(WiFiDirect.TAG, 
              "Recorder stopped and released prematurely"); 
           } 
          } 

          @Override 
          public void onMarkerReached(AudioRecord recorder) { 
           // TODO Auto-generated method stub 

          } 
         }); 

       aRecorder.startRecording(); 
       Log.d(WiFiDirect.TAG, "start Recording"); 
       aRecorder.read(data, 0, bufferSize); 
       for (int i = 0; i < data.length; i++) { 
        dosFile.writeShort(data[i]); 
       } 

       if (aRecorder.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) { 
        aRecorder.stop(); 
        aRecorder.release(); 
        setIsDone(true); 
        Log.d(WiFiDirect.TAG, "Recorder stopped and released"); 
       } 

      } catch (Exception e) { 
       // TODO: handle exception 
      } 
     } 
    } 
5

Bây giờ tôi thực hiện buổi ghi hình bằng cách sử dụng "OnRecordPositionUpdateListener". Điều này có vẻ rất hứa hẹn và đúng cách để thực hiện theo Tài liệu SDK. Mọi thứ dường như hoạt động (mở thiết bị âm thanh, đọc() ing dữ liệu, v.v ...) nhưng Người nghe là chưa bao giờ được gọi.

Có ai biết tại sao không?

Tôi thấy rằng OnRecordPositionUpdateListener bị bỏ qua cho đến khi bạn thực hiện .read() đầu tiên của mình.

Nói cách khác, tôi thấy rằng nếu tôi thiết lập mọi thứ trên mỗi tài liệu, Trình nghe của tôi sẽ không bao giờ được gọi. Tuy nhiên, nếu lần đầu tiên tôi gọi số .read() ngay sau khi thực hiện .start() ban đầu thì Trình nghe sẽ được gọi - miễn là tôi đã thực hiện một số .read() mỗi khi người nghe được gọi.

Nói cách khác, hầu như có vẻ như sự kiện Trình nghe chỉ tốt cho một lần cho mỗi .read() hoặc một số loại như vậy.

Tôi cũng thấy rằng nếu tôi yêu cầu ít hơn các mẫu buffSize/2 để đọc, thì Trình nghe sẽ không được gọi. Vì vậy, có vẻ như người nghe chỉ được gọi là SAU .read() của ít nhất một nửa kích thước bộ đệm. Để tiếp tục sử dụng tính năng gọi lại của Trình nghe, người gọi phải đọc từng lần người nghe đang chạy. (Nói cách khác, hãy gọi để đọc trong mã Trình nghe.)

Tuy nhiên, người nghe dường như được gọi vào thời điểm dữ liệu chưa sẵn sàng, gây chặn.

Ngoài ra, nếu khoảng thời gian thông báo hoặc thời gian của bạn lớn hơn một nửa bộ đệm của bạnSize chúng sẽ không bao giờ được gọi, có vẻ như vậy.

UPDATE:

Như tôi tiếp tục đào sâu hơn, tôi đã phát hiện ra rằng khi gọi lại dường như chỉ được gọi khi một .read() kết thúc ......!

Tôi không biết đó là lỗi hay tính năng. Suy nghĩ ban đầu của tôi là tôi muốn gọi lại khi đã đến lúc đọc. Nhưng có thể các nhà phát triển Android đã có ý tưởng theo cách khác, nơi bạn chỉ cần đặt một while(1){xxxx.read(...)} trong một chủ đề và để giúp bạn không phải theo dõi từng lần đọc() kết thúc, gọi lại về cơ bản có thể cho bạn biết khi đọc xong.

Oh. Có lẽ nó đã làm tôi bực dọc. Và tôi nghĩ rằng những người khác đã chỉ ra điều này trước khi tôi ở đây nhưng nó đã không chìm trong: Vị trí hoặc thời gian gọi lại phải hoạt động trên các byte đã được đọc ...

Tôi đoán có lẽ tôi đang mắc kẹt với việc sử dụng các chủ đề .

Trong kiểu này, người ta sẽ có một chuỗi liên tục gọi đọc() ngay sau khi nó được trả về, vì đọc bị chặn và kiên nhẫn chờ đợi đủ dữ liệu để trả lại.

Sau đó, độc lập, cuộc gọi lại sẽ gọi hàm được chỉ định của bạn mỗi số x mẫu.

+1

Cảm ơn bạn đã nghiên cứu. Nó giúp! Một chút nữa về vấn đề này: callback onPeriodicNotification không bao giờ được gọi trên API 16, 17, 18, trừ khi một lệnh gọi() chỉ sau khi bắt đầu. Tuy nhiên, có vẻ như vấn đề được khắc phục trên API> = 19. –

+0

DmitryO, Cảm ơn bạn rất nhiều vì đã có thêm thông tin! Tôi không thể chờ đợi để thử nó. –

Các vấn đề liên quan