Запись и воспроизведение звука в реальном времени на Android, обработка потоков и обратного вызова

Я хочу записать живой звук и воспроизвести его. Что касается пользовательского интерфейса, в приложении всего три кнопки: одна для начала записи и потоковой передачи, одна для воспроизведения предварительно записанного файла и последняя для остановки текущей задачи (запись / играет). Для этого я использовал классы AudioRecord и AudioTrack для записи и воспроизведения соответственно. Моя программа выглядит так ....

/ ** * @author amit * * /

public class AudioRecorder extends Activity {
    private String LOG_TAG = null;

    /* variables which are required to generate and manage the UI of the App */
    // private RecordButton mRecordButton = null;
    private Button recordBtn, stopBtn, playBtn;

    /*
     * variables which are required for the actual functioning of the recording
     * and playing
     */
    private AudioRecord recorder = null;
    private AudioTrack player = null;
    private AudioManager audioManager = null;
    private int recorderBufSize, recordingSampleRate;
    private int trackBufSize;
    private short[] audioData;
    private boolean isRecording = false, isPlaying = false;
    private Thread startRecThread;

    private AudioRecord.OnRecordPositionUpdateListener posUpdateListener;

    /**
     * constructor method for initializing the variables
     */
    public AudioRecorder() {
        super();
        LOG_TAG = "Constructor";
        recorderBufSize = recordingSampleRate = trackBufSize = 0;

        // init function will initialize all the necessary variables ...
        init();

        if (recorder != null && player != null) {
            Log.e(LOG_TAG, "recorder and player initialized");
            audioData = new short[recorderBufSize / 2]; // since we r reading shorts

        } else {
            Log.e(LOG_TAG, "Problem inside init function ");
        }
        posUpdateListener = new AudioRecord.OnRecordPositionUpdateListener() {
            int numShortsRead = 0;

            @Override
            public void onPeriodicNotification(AudioRecord rec) {
                // TODO Auto-generated method stub
//              String LOG_TAG = Thread.currentThread().getName();
//               Log.e(LOG_TAG, "inside position listener");
                audioData = new short[recorderBufSize / 2]; // divide by 2 since now we are reading shorts 
                numShortsRead = rec.read(audioData, 0, audioData.length);
                player.write(audioData, 0, numShortsRead);

            }

            @Override
            public void onMarkerReached(AudioRecord recorder) {
                // TODO Auto-generated method stub
                Log.e(LOG_TAG, "Marker Reached");
            }
        };
        // listener will be called every time 160 frames are reached
        recorder.setPositionNotificationPeriod(160);
        recorder.setRecordPositionUpdateListener(posUpdateListener);

        Log.e(LOG_TAG, "inside constructor");
    }

    private void init() {
        LOG_TAG = "initFunc";
        // int[] mSampleRates = new int[] { 8000, 11025, 22050, 44100 };
        short audioFormat = AudioFormat.ENCODING_PCM_16BIT;
        // for (int rate : mSampleRates) {
        this.recordingSampleRate = AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC);
        try {
            // Log.d(LOG_TAG, "Attempting rate " + rate + "Hz, bits: " +
            // audioFormat);
            int bufrSize = AudioRecord.getMinBufferSize(this.recordingSampleRate,
                    AudioFormat.CHANNEL_IN_MONO, audioFormat);

                // lets find out the minimum required size for AudioTrack
            int audioTrackBufSize = AudioTrack.getMinBufferSize(this.recordingSampleRate,
                    AudioFormat.CHANNEL_OUT_MONO, audioFormat);

            if (bufrSize != AudioRecord.ERROR_BAD_VALUE
                    && bufrSize != AudioRecord.ERROR) {
                // check if we can instantiate and have a success
                if(audioTrackBufSize >= bufrSize){
                    this.recorderBufSize = audioTrackBufSize;
                }else{
                    this.recorderBufSize = bufrSize;
                }

                AudioRecord rec = new AudioRecord(
                        MediaRecorder.AudioSource.DEFAULT, this.recordingSampleRate,
                        AudioFormat.CHANNEL_IN_MONO, audioFormat, this.recorderBufSize);

                if (rec != null
                        && rec.getState() == AudioRecord.STATE_INITIALIZED) {

                    // storing variables for future use . . .
//                  this.recordingSampleRate = rate;
//                  this.recorderBufSize = bufrSize;

                    Log.e(LOG_TAG,
                            "Returning..(rate:channelConfig:audioFormat:recorderBufSize)"
                                    + this.recordingSampleRate + ":" + AudioFormat.CHANNEL_IN_MONO
                                    + ":" + audioFormat + ":" + this.recorderBufSize);

                    // Now create an instance of the AudioTrack
//                  int audioTrackBufSize = AudioTrack.getMinBufferSize(rate,
//                          AudioFormat.CHANNEL_OUT_MONO, audioFormat);

                    Log.e(LOG_TAG, "Audio Record / Track / Final buf size :" + bufrSize + "/ " +audioTrackBufSize + "/ "+this.recorderBufSize);


                    this.player = new AudioTrack(AudioManager.STREAM_MUSIC,
                            this.recordingSampleRate, AudioFormat.CHANNEL_OUT_MONO, audioFormat,
                            this.recorderBufSize, AudioTrack.MODE_STREAM);

                    this.recorder = rec;
                    this.player.stop();
                    this.player.flush();
                    this.player.setPlaybackRate(this.recordingSampleRate);
                    return;
                }
            }
        } catch (IllegalArgumentException e) {
            Log.d(LOG_TAG, this.recordingSampleRate + "Exception, keep trying.", e);
        } catch (Exception e) {
            Log.e(LOG_TAG, this.recordingSampleRate + "Some Exception!!", e);
        }
        // for loop for channel config ended here. . . .
        // for loop for audioFormat ended here. . .
        // }// for loop for sampleRate
        return;
    }

    private void startPlaying() {
        LOG_TAG = "startPlaying";

        Log.e(LOG_TAG, "start Playing");
    }

    private void stopPlaying() {
        LOG_TAG = "stopPlaying";

        Log.e(LOG_TAG, "stop Playing");
    }

    private void startRecording() {
        LOG_TAG = "startRecording"; 

        /* start a separate recording thread from here . . . */
        startRecThread = new Thread() {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                android.os.Process
                        .setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);
//              String LOG_TAG = Thread.currentThread().getName();
                if(recorder.getRecordingState() != AudioRecord.RECORDSTATE_RECORDING){
                    recorder.startRecording();
                }
//              Log.e(LOG_TAG, "running" +recorder.getRecordingState());
                while (recorder.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) {
                    recorder.read(audioData, 0, audioData.length);
                    try {

                         Thread.sleep(1000); // sleep for 2s
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        Log.e("run Method", "recorder thread is interrupted");
                        e.printStackTrace();
                    }
                }
            }
        };
        setVolumeControlStream(AudioManager.STREAM_MUSIC);
        audioManager.setSpeakerphoneOn(false);
        player.flush();     
        player.play();
        startRecThread.start();

        Log.e(LOG_TAG, "start Recording");
    }

    private void stopRecording() {
        LOG_TAG = "stopRecording";
        recorder.stop();

        if (startRecThread != null && startRecThread.isAlive()) {           
            startRecThread.destroy();
            startRecThread = null;
        }

        player.stop();
        player.flush();
        Log.e(LOG_TAG, "stop Recording");
    }

    private void stop() {
        if (isRecording) {
            isRecording = false;
            stopRecording();
        }
        if (isPlaying) {
            isPlaying = false;
            stopPlaying();
        }
        recordBtn.setEnabled(true);
        playBtn.setEnabled(true);
    }

    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        LOG_TAG = "onCreate";
//      Log.e(LOG_TAG, "Create Called");
        // getting the audio service
        audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
        LinearLayout ll = new LinearLayout(this);

        // creating Buttons one by one . . . .
        // button to start the recording process
        recordBtn = new Button(this);
        recordBtn.setText("Record");
        recordBtn.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                recordBtn.setEnabled(false);
                playBtn.setEnabled(false);
                isRecording = true;
                startRecording();
            }
        });
        // single button to stop recording and playing as applicable
        stopBtn = new Button(this);
        stopBtn.setText("Stop");
        stopBtn.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                stop();
            }
        });
        // button to play the recorded sound
        playBtn = new Button(this);
        playBtn.setText("Play");
        playBtn.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                // reverse the isPlaying
                isPlaying = true;
                recordBtn.setEnabled(false);
                playBtn.setEnabled(false);
                startPlaying();
            }
        });

        ll.addView(recordBtn, new LinearLayout.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT, 1));

        ll.addView(playBtn, new LinearLayout.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT, 1));

        ll.addView(stopBtn, new LinearLayout.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT, 1));

        setContentView(ll);
    }

    @Override
    protected void onDestroy() {
        // Clean up code . ..
        super.onDestroy();
        if (recorder != null)
            recorder.release();
        if (startRecThread!=null && startRecThread.isAlive())
            startRecThread.destroy();
        if (recorder != null)
            recorder.release();
        if (player != null)
            player.release();
        startRecThread = null;
        recorder = null;
        player = null;
        recordBtn = null;
        stopBtn = null;
        playBtn = null;
        audioData = null;
        System.gc();
    }

}

Как видите, функции startPlaying() и stopPlaying() еще не реализованы, поэтому не будем о них говорить. В настоящее время я просто пытаюсь записывать и воспроизводить. Когда я запускаю программу, она воспроизводит записанный звук, но звук появляется издалека. Другая проблема заключается в том, что поток пользовательского интерфейса приложения зависает, хотя у меня есть отдельный поток для чтения аудио. Пожалуйста помоги....


person Amit    schedule 23.02.2012    source источник
comment
выберите одну скорость, конфигурацию и формат. Затем придерживайтесь его или заставьте функцию инициализации использовать свойство и переключаться между ними.   -  person L7ColWinters    schedule 23.02.2012
comment
@ L7ColWinters спасибо и просмотрите отредактированный вопрос ....   -  person Amit    schedule 28.02.2012
comment
есть ли у вас желаемая функциональность, чтобы иметь возможность сказать что-то во время записи и автоматически воспроизвести это, не нажимая кнопку воспроизведения? Это то, что делает ваш текущий код, хотя он зависает и воспроизводит очень высокий звук, что может быть обратной связью.   -  person L7ColWinters    schedule 28.02.2012
comment
@ L7ColWinters, да, это все, что я хочу (чтобы записать и воспроизвести его, кнопка воспроизведения будет использоваться для воспроизведения файла, который еще не реализован), у вас мои проблемы, почему моя программа зависает и воспроизводит такой плохой звук ... . нужно ли мне использовать одинаковый размер буфера для AudioRecord и AudioTrack? пожалуйста, помогите ...   -  person Amit    schedule 29.02.2012
comment
@ L7ColWinters, пожалуйста, посмотрите обновленный startRecording () .... в котором я вызываю Thread.sleep (20), он засыпает поток рекордера на 20 мс (я где-то читал, что 20 мс - идеальная задержка ... для такого типа приложений. .... Еще раз спасибо...   -  person Amit    schedule 29.02.2012
comment
Кажется, что это в некотором рабочем состоянии, когда я сделал и рекордер, и плеер с одинаковым размером буфера ... Однако поток пользовательского интерфейса все еще зависает (и мне интересно, почему?), Хотя я создал отдельный поток для записи ... вы можете помочь в этом Это ?   -  person Amit    schedule 29.02.2012
comment
Не могли бы вы опубликовать свой рабочий код. Я не могу сделать это с 2 дня .. или дайте мне ссылку, по которой я могу получить помощь. Спасибо. +100 за этот вопрос.   -  person Rahul Baradia    schedule 16.05.2012
comment
@ Tech.Rahul, код, вставленный в ответ конфеты, также работает, однако у меня есть код сравнения, так как мое приложение довольно большое ... Я предлагаю сначала попробовать код конфеты, и если это не сработает. Я вставлю свой Код как отдельный ответ ... Удачи, приятель, и не нервничай, это займет время на начальном уровне.   -  person Amit    schedule 17.05.2012
comment
@anDroider - большое спасибо .. я попробую и дам знать ..   -  person Rahul Baradia    schedule 17.05.2012
comment
@anDroider: не могли бы вы опубликовать свой код. Я не могу это решить. Спасибо..   -  person Rahul Baradia    schedule 18.05.2012
comment
Если вы установили необходимое разрешение в AndroidManifyt.xml, я назначил следующие разрешения ‹uses-permission android: name = android.permission.RECORD_AUDIO /› ‹uses-permission android: name = android.permission.WRITE_EXTERNAL_STORAGE /›   -  person Amit    schedule 18.05.2012
comment
Я использую это .. не могли бы вы опубликовать свой код ..   -  person Rahul Baradia    schedule 18.05.2012
comment
stackoverflow .com / questions / 10619675 / .... это мой вопрос, который я задал. Я получаю некоторые ошибки, вы можете мне помочь. Благодарность   -  person Rahul Baradia    schedule 18.05.2012


Ответы (2)


Если ваше требование заключается в том, что во время записи он должен воспроизводиться (означает зацикливание звука), в потоке цикла while вы сохраняете записанные данные (буфер audioData), там вы сами можете скопировать их в объект проигрывателя в цикле while ( player.write (audioData, 0, numShortsRead);). Вы сказали, что ваш поток пользовательского интерфейса застрял, это может быть из-за того, что вы уделяете больше внимания потоку аудиозаписи.

Проверьте приведенный ниже код, который я использовал для вышеуказанного требования возврата к циклу.

boolean m_isRun=true;
public void loopback() {
        // Prepare the AudioRecord & AudioTrack
        try {
            buffersize = AudioRecord.getMinBufferSize(SAMPLE_RATE,
                    AudioFormat.CHANNEL_CONFIGURATION_MONO,
                    AudioFormat.ENCODING_PCM_16BIT);

            if (buffersize <= BUF_SIZE) {
                buffersize = BUF_SIZE;
            }
            Log.i(LOG_TAG,"Initializing Audio Record and Audio Playing objects");

            m_record = new AudioRecord(MediaRecorder.AudioSource.MIC,
                    SAMPLE_RATE, AudioFormat.CHANNEL_CONFIGURATION_MONO,
                    AudioFormat.ENCODING_PCM_16BIT, buffersize * 1);

            m_track = new AudioTrack(AudioManager.STREAM_ALARM,
                    SAMPLE_RATE, AudioFormat.CHANNEL_CONFIGURATION_MONO,
                    AudioFormat.ENCODING_PCM_16BIT, buffersize * 1,
                    AudioTrack.MODE_STREAM);

            m_track.setPlaybackRate(SAMPLE_RATE);
        } catch (Throwable t) {
            Log.e("Error", "Initializing Audio Record and Play objects Failed "+t.getLocalizedMessage());
        }

        m_record.startRecording();
        Log.i(LOG_TAG,"Audio Recording started");
        m_track.play();
        Log.i(LOG_TAG,"Audio Playing started");

        while (m_isRun) {
            m_record.read(buffer, 0, BUF_SIZE);
            m_track.write(buffer, 0, buffer.length);
        }

        Log.i(LOG_TAG, "loopback exit");
    }

    private void do_loopback() {
        m_thread = new Thread(new Runnable() {
            public void run() {
                loopback();
            }
        });

Еще одна вещь, если ваше требование - запись в течение нескольких секунд, а затем воспроизведение, во время воспроизведения ваша запись должна начаться снова, вы можете сделать это с потоком обработчика задержки с тайм-аутом. В этом потоке вы можете остановить запись, скопируйте буфер , затем начните запись.

person candy    schedule 01.03.2012
comment
как вы определяете значение BUF_SIZE? Я также использовал positionUpdateListener (), каково ваше мнение о его важности ... и еще кое-что, что является условием остановки цикла while? а у вас основная нить зависает в процессе? - person Amit; 01.03.2012
comment
для условия остановки я использую keyevent из команд adb в соответствии с моим требованием. В этом случае я устанавливаю для m_isRun значение false, вы можете использовать поток тайм-аута или событие кнопки. Я не думаю, что основной поток останавливается, раньше я использовал события кнопок вместо ключевых событий. если это именно требование обратной связи, я не думаю, что также требуется обновление позиции. - person candy; 01.03.2012
comment
И в случае BUF_SIZE, когда я просто использовал getMinBufferSize (), он выдавал недостаточные проблемы с буфером. где-то в сетевых ссылках упоминается, что размер буфера должен быть больше SAMPLE_RATE. Я иду на это. Но не могу сказать, что это идеальное решение проблемы с размером буфера. - person candy; 01.03.2012
comment
Вы можете просто объяснить, для чего нужна эта переменная m_isRun? Я также хочу записывать и воспроизводить звук одновременно, и я новичок в этой области, поэтому мне нужна небольшая помощь :) +1 за ваш ответ - person Sandra; 14.11.2012
comment
m_isRun - простая логическая переменная, которую я использовал для запуска / остановки записи-воспроизведения - person candy; 14.11.2012
comment
Я последовал вашему примеру, и мое приложение работает около 2 секунд, если я что-то говорю, я слышу свой голос, и внезапно поток завершается, вы ничего не слышите, и это сообщение отображается снова и снова в журнале: receiveBuffer () track 0x16ab30 отключен, перезапуск. Были ли у вас подобные трудности или вы знали, как их решить? Спасибо за ответ и беспокойство. - person Sandra; 14.11.2012
comment
попробуйте изменить размер буфера. - person candy; 15.11.2012
comment
Я пробовал этот код, но при использовании нарастает шум и эхо. Как этого избежать? - person Arunraj Shanmugam; 27.12.2017

Я предлагаю использовать задачу Async, также известную как безболезненная потоковая передача. Это лучший способ использовать потоки в Android. Вы можете использовать фоновую обработку, методы предварительного и последующего выполнения, разделив существующую программу.

http://android-developers.blogspot.ca/2009/05/pxygen-threading.html

person MACMAN    schedule 02.03.2012