AudioTrack - AudioTack не воспроизводит полностью записанный звук

У меня возникла проблема, из-за которой, когда я записал звук и нажал кнопку «Воспроизвести», он воспроизводится только около 3 секунд, а не полностью записанный звук. Я попытался увеличить размер буфера, но вместо этого это приводит к сбою моего приложения. Кто-нибудь может посоветовать, пожалуйста? Спасибо

Below is my code
package com.exercise.AndroidAudioRecord;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import android.app.Activity;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.Spinner;
import android.widget.Toast;

public class AndroidAudioRecordActivity extends Activity {

    String[] freqText = {"11.025 KHz (Lowest)", "16.000 KHz", "22.050 KHz", "44.100 KHz (Highest)"};
    Integer[] freqset = {11025, 16000, 22050, 44100};
    private ArrayAdapter<String> adapter;

    Spinner spFrequency;
    Button startRec, stopRec, playBack;

    Boolean recording;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        startRec = (Button)findViewById(R.id.startrec);
        stopRec = (Button)findViewById(R.id.stoprec);
        playBack = (Button)findViewById(R.id.playback);

        startRec.setOnClickListener(startRecOnClickListener);
        stopRec.setOnClickListener(stopRecOnClickListener);
        playBack.setOnClickListener(playBackOnClickListener);

        spFrequency = (Spinner)findViewById(R.id.frequency);
        adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, freqText);
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        spFrequency.setAdapter(adapter);

        stopRec.setEnabled(false);
    }

    OnClickListener startRecOnClickListener
    = new OnClickListener(){

        @Override
        public void onClick(View arg0) {

            Thread recordThread = new Thread(new Runnable(){

                @Override
                public void run() {
                    recording = true;
                    startRecord();
                }

            });

            recordThread.start();
            startRec.setEnabled(false);
            stopRec.setEnabled(true);

        }};

    OnClickListener stopRecOnClickListener
    = new OnClickListener(){

        @Override
        public void onClick(View arg0) {
            recording = false;
            startRec.setEnabled(true);
            stopRec.setEnabled(false);
        }};

    OnClickListener playBackOnClickListener
        = new OnClickListener(){

            @Override
            public void onClick(View v) {
                playRecord();
            }

    };

    private void startRecord(){

        File file = new File(Environment.getExternalStorageDirectory(), "test.pcm"); 

        int selectedPos = spFrequency.getSelectedItemPosition();
        int sampleFreq = freqset[selectedPos];

        final String promptStartRecord = 
                "startRecord()\n"
                + file.getAbsolutePath() + "\n"
                + (String)spFrequency.getSelectedItem();

        runOnUiThread(new Runnable(){

            @Override
            public void run() {
                Toast.makeText(AndroidAudioRecordActivity.this, 
                        promptStartRecord, 
                        Toast.LENGTH_LONG).show();
            }});

        try {
            file.createNewFile();

            OutputStream outputStream = new FileOutputStream(file);
            BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
            DataOutputStream dataOutputStream = new DataOutputStream(bufferedOutputStream);

            int minBufferSize = AudioRecord.getMinBufferSize(sampleFreq, 
                    AudioFormat.CHANNEL_IN_STEREO, 
                    AudioFormat.ENCODING_PCM_16BIT);

            short[] audioData = new short[minBufferSize];

            AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
                    sampleFreq,
                    AudioFormat.CHANNEL_OUT_STEREO,
                    AudioFormat.ENCODING_PCM_16BIT,
                    minBufferSize);

            audioRecord.startRecording();

            while(recording){
                int numberOfShort = audioRecord.read(audioData, 0, minBufferSize);
                for(int i = 0; i < numberOfShort; i++){
                    dataOutputStream.writeShort(audioData[i]);
                }
            }

            audioRecord.stop();
            audioRecord.release();
            dataOutputStream.close();

        } catch (IOException e) {
            e.printStackTrace();
        }

    }




    void playRecord(){

        File file = new File(Environment.getExternalStorageDirectory(), "test.pcm");

        int shortSizeInBytes = Short.SIZE/Byte.SIZE;
        int bufferSizeInBytes = (int)(file.length()/shortSizeInBytes);

        short[] audioData = new short[bufferSizeInBytes];
        //int  buflen=bufferSizeInBytes/2; 
        try {
            InputStream inputStream = new FileInputStream(file);
            BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
            DataInputStream dataInputStream = new DataInputStream(bufferedInputStream);

            int i = 0;
            while(dataInputStream.available() > 0){
                audioData[i] = dataInputStream.readShort();
                i++;
            }

            dataInputStream.close();

            int selectedPos = spFrequency.getSelectedItemPosition();
            int sampleFreq = freqset[selectedPos];
            int sampleFreq1 = 12000;
            int sampleFreq2 = 15000;
            final String promptPlayRecord = 
                    "PlayRecord()\n"
                    + file.getAbsolutePath() + "\n"
                    + (String)spFrequency.getSelectedItem();

            Toast.makeText(AndroidAudioRecordActivity.this, 
                    promptPlayRecord, 
                    Toast.LENGTH_LONG).show();

            AudioTrack audioTrack = new AudioTrack(
                    AudioManager.STREAM_MUSIC,
                    sampleFreq1,
                    AudioFormat.CHANNEL_OUT_STEREO,
                    AudioFormat.ENCODING_PCM_16BIT,
                    bufferSizeInBytes,
                    AudioTrack.MODE_STREAM);

            int stereo=audioTrack.setStereoVolume(0.0f, 1.0f);

            audioTrack.write(audioData, 0, bufferSizeInBytes);
            audioTrack.play();


            AudioTrack audioTrack2 = new AudioTrack(
                    AudioManager.STREAM_MUSIC,
                    sampleFreq2,
                    AudioFormat.CHANNEL_OUT_STEREO,
                    AudioFormat.ENCODING_PCM_16BIT,
                    bufferSizeInBytes,
                    AudioTrack.MODE_STREAM);

            int stereo2=audioTrack.setStereoVolume(1.0f, 0.0f);

            audioTrack2.write(audioData, 0, bufferSizeInBytes);
            audioTrack2.play();




        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Спасибо всем!


person user1730935    schedule 20.09.2015    source источник


Ответы (3)


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

Я вижу одну проблему в коде воспроизведения, которая приведет к воспроизведению 1/2 ожидаемой длины.

int shortSizeInBytes = Short.SIZE/Byte.SIZE;
int bufferSizeInBytes = (int)(file.length()/shortSizeInBytes);

bufferSizeInBytes должно быть просто file.length(), а fileLength/2 - это количество выборок.

Когда вы определяете short[] audioData, вы хотите использовать количество сэмплов:

short[] audioData = new short[file.length()/2];

И там, где вы создаете AudioTrack, вы захотите использовать количество байтов:

AudioTrack audioTrack = new AudioTrack(
                AudioManager.STREAM_MUSIC,
                sampleFreq1,
                AudioFormat.CHANNEL_OUT_STEREO,
                AudioFormat.ENCODING_PCM_16BIT,
                file.length(),
                AudioTrack.MODE_STREAM);

И, наконец, там, где вы вызываете audioTrack.write, вам нужно снова использовать количество сэмплов.

РЕДАКТИРОВАТЬ: Из трассировки стека, которую вы добавили к вопросу, ясно, что конструктор AudioTrack дает сбой с ошибкой. Вы можете проверить эту ошибку, вызвав audioTrack.status после вызова конструктора. Я могу придумать пару возможных проблем.

Во-первых, вы указываете AudioTrack.MODE_STREAM, что обычно означает, что вы собираетесь указать небольшой (возможно, несколько килобайт) буфер для звуковой дорожки, а затем последовательно вызывать функцию write для воспроизведения всего исходного буфера. AudioTrack.MODE_STATIC, с другой стороны, используется для воспроизведения всего буфера за один раз - как вы пытаетесь это сделать. Учитывая, что целью MODE_STREAM является использование наименьшего возможного буфера, можно предположить, что указание размера буфера 1,64 МБ (20 секунд * 2 байта на выборку * 44100) не поддерживается. Итак, первое, что я бы сделал, это изменил режим на MODE_STATIC. Я недостаточно хорошо знаю API, чтобы сказать вам, поддерживается ли такой большой размер буфера в этом режиме. Если нет, то вам придется транслировать должным образом.

person jaket    schedule 21.09.2015
comment
Привет, спасибо за вашу помощь - person user1730935; 21.09.2015
comment
Привет, спасибо за вашу помощь. Я отредактировал код, как вы упомянули. Но если я попытаюсь записать звук примерно за 20 секунд и нажму кнопку воспроизведения, приложение просто вылетит. Почему это происходит? Это из-за моего размера буфера недостаточно? Привет, спасибо за вашу помощь. Я отредактировал код, как вы упомянули. Но если я попытаюсь записать звук примерно за 20 секунд и нажму кнопку воспроизведения, приложение просто вылетит. Почему это происходит? audioTrack.write(audioData, 0, (int)(file.length()/2)); аудиотрек.play(); - person user1730935; 21.09.2015
comment
Просто вылетает довольно расплывчато. Какое исключение выбрасывается и из какой строки? - person jaket; 21.09.2015
comment
Привет, я добавил более подробную информацию об ошибке в свой пост. Очень ценю ваш совет! Спасибо! - person user1730935; 21.09.2015

Вывод логкэта

09-22 01:23:42.940: I/Reverb(22286):  getpid() 22286, IPCThreadState::self()->getCallingPid() 22286
09-22 01:23:42.955: E/AudioTrack(22286): AudioFlinger could not create track, status: -12
09-22 01:23:42.955: E/AudioTrack-JNI(22286): Error initializing AudioTrack
09-22 01:23:42.965: E/AudioTrack-Java(22286): [ android.media.AudioTrack ] Error code -20 when initializing AudioTrack.
09-22 01:23:42.965: D/AndroidRuntime(22286): Shutting down VM
09-22 01:23:42.965: W/dalvikvm(22286): threadid=1: thread exiting with uncaught exception (group=0x40eed2a0)
09-22 01:23:42.985: E/AndroidRuntime(22286): FATAL EXCEPTION: main
09-22 01:23:42.985: E/AndroidRuntime(22286): java.lang.IllegalStateException: play() called on uninitialized AudioTrack.
09-22 01:23:42.985: E/AndroidRuntime(22286):    at android.media.AudioTrack.play(AudioTrack.java:887)
09-22 01:23:42.985: E/AndroidRuntime(22286):    at com.example.android2.MainActivity.playRecord(MainActivity.java:225)
09-22 01:23:42.985: E/AndroidRuntime(22286):    at com.example.android2.MainActivity$3.onClick(MainActivity.java:99)
09-22 01:23:42.985: E/AndroidRuntime(22286):    at android.view.View.performClick(View.java:4211)
09-22 01:23:42.985: E/AndroidRuntime(22286):    at android.view.View$PerformClick.run(View.java:17267)
09-22 01:23:42.985: E/AndroidRuntime(22286):    at android.os.Handler.handleCallback(Handler.java:615)
09-22 01:23:42.985: E/AndroidRuntime(22286):    at android.os.Handler.dispatchMessage(Handler.java:92)
09-22 01:23:42.985: E/AndroidRuntime(22286):    at android.os.Looper.loop(Looper.java:137)
09-22 01:23:42.985: E/AndroidRuntime(22286):    at android.app.ActivityThread.main(ActivityThread.java:4898)
09-22 01:23:42.985: E/AndroidRuntime(22286):    at java.lang.reflect.Method.invokeNative(Native Method)
09-22 01:23:42.985: E/AndroidRuntime(22286):    at java.lang.reflect.Method.invoke(Method.java:511)
09-22 01:23:42.985: E/AndroidRuntime(22286):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1006)
09-22 01:23:42.985: E/AndroidRuntime(22286):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:773)
09-22 01:23:42.985: E/AndroidRuntime(22286):    at dalvik.system.NativeStart.main(Native Method)

Ниже приведен измененный код

void playRecord(){

        File file = new File(Environment.getExternalStorageDirectory(), "test.pcm");

        short[] audioData = new short[(int) (file.length()/2)];
        try {
            InputStream inputStream = new FileInputStream(file);
            BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
            DataInputStream dataInputStream = new DataInputStream(bufferedInputStream);

            int i = 0;
            while(dataInputStream.available() > 0){
                audioData[i] = dataInputStream.readShort();
                i++;
            }

            dataInputStream.close();

            int selectedPos = spFrequency.getSelectedItemPosition();
            int sampleFreq = freqset[selectedPos];
            int sampleFreq1 = 44100;
            int sampleFreq2 = 44400;
            final String promptPlayRecord = 
                    "PlayRecord()\n"
                    + file.getAbsolutePath() + "\n"
                    + (String)spFrequency.getSelectedItem();

            Toast.makeText(MainActivity.this, 
                    promptPlayRecord, 
                    Toast.LENGTH_LONG).show();

            AudioTrack audioTrack = new AudioTrack(
                    AudioManager.STREAM_MUSIC,
                    sampleFreq1,
                    AudioFormat.CHANNEL_OUT_STEREO,
                    AudioFormat.ENCODING_PCM_16BIT,
                    (int)file.length(),
                    AudioTrack.MODE_STREAM);

            int stereo=audioTrack.setStereoVolume(0.0f, 1.0f);

            audioTrack.write(audioData, 0, (int)(file.length()/2));
            audioTrack.play();

            AudioTrack audioTrack2 = new AudioTrack(
                    AudioManager.STREAM_MUSIC,
                    sampleFreq2,
                    AudioFormat.CHANNEL_OUT_STEREO,
                    AudioFormat.ENCODING_PCM_16BIT,
                    (int)file.length(),
                    AudioTrack.MODE_STREAM);

            int stereo2=audioTrack2.setStereoVolume(1.0f, 0.0f);

            audioTrack2.write(audioData, 0, (int)(file.length()/2));
            audioTrack2.play();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
person user1730935    schedule 21.09.2015
comment
Вы пытаетесь одновременно создать две аудиодорожки на одном устройстве вывода. - person AterLux; 22.09.2015

Здесь вы читаете блок данных только один раз.

        while(dataInputStream.available() > 0){
            audioData[i] = dataInputStream.readShort();
            i++;
        }

Поскольку доступным является не остаточный размер, а размер, доступный для чтения без блокировки (т.е. он может быть равен нулю, в то время как больше данных осталось непрочитанными), вам нужно читать до тех пор, пока метод read не вернет меньше нуля (т.е. EOF). Например:

// Do here AudioTrack initialization
// ...
InputStream inputStream = new FileInputStream(file);
byte[] byteBuf = new byte[32768]; // just any even size
short[] shortBuf = new short[byteBuf.length / 2]; // buffer of shorts
audioTrack.play(); // start playback. Will wait until first data is written
for(;;) {
    int l = inputStream.read(byteBuf);
    if (l < 0) break; // finish reading when EOF is truly reached 
    int o = 0;
    for (int i = 0; i < l; i += 2) {
        shortBuf[o] = (short)((byteBuf[i] << 8) | (byteBuf[i + 1] & 0xFF)); // convert two big-endian bytes to one short
        o++;
    }
    int res = audioTrack.write(shortBuf, 0, o); // write next portion
    if (res < 0) { 
         // handle audio writing error;
         break;
    }
}
// ...
person AterLux    schedule 21.09.2015