Android: Audio Track (Stream-Mode) перерывает запись - PullRequest
0 голосов
/ 16 января 2019

Моя главная цель - иметь возможность передавать аудио с одного устройства на другое устройство в локальной сети.Я планирую сделать это, прочитав mp3-файл в байт [] (который у меня уже работает) и отправив его как udp-пакет на 2-е устройство и воспроизведя его там (я говорю вам об этом на случай, если это уженеправильный подход).В настоящее время я застрял с игрой моих байтовых массивов.Я читаю мой файл с функцией decoder(path, startMs, durationMs) из mp3.В данный момент я могу слышать звук, но после каждого тика (части, в которых я читаю файл) я слышу ничего в течение нескольких мс, что приводит к плохому завершению прослушивания.Я думал, что это связано с размером буфера, и попытался немного поиграть с ним, но это ничего не изменило, а также добавило AudioTrack.WRITE_NON_BLOCKING.Я также подумал о том, чтобы поместить цикл for () в другой поток, но это не работает вообще (что имеет смысл).Я также уже пытался сначала прочитать файл и поместить свой байт [] в Arraylist, так как это может быть причиной проблемы из-за медленного чтения файла, но все равно тот же опыт.Это также может помочь узнать, что Log.e("DEBUG", "Length " + data.length); отображается только на каждом тике, что означает, что запись также происходит только на каждом тике (что, вероятно, является проблемой).Как мне избавиться от этих пустых частей в моей песне?

Вот мой код, выполняемый при нажатии кнопки:

song.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Thread thrd = new Thread(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                int tick = 1000;
                                int max = 9000;
                                int sampleRate = 44100;
                                int bufSize = AudioTrack.getMinBufferSize(sampleRate*4, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT);
                                byte[] data = decode(path, 0, tick);
                                AudioTrack track = new AudioTrack(AudioManager.STREAM_MUSIC,
                                        44100, AudioFormat.CHANNEL_OUT_STEREO,
                                        AudioFormat.ENCODING_PCM_16BIT, bufSize,
                                        AudioTrack.MODE_STREAM, AudioTrack.WRITE_NON_BLOCKING);
                                track.play();
                                track.write(data, 0, data.length);
                                Log.e("DEBUG", "Length " + data.length);
                                for(int i = tick; i < max; i+=tick) {
                                    data = decode(path, i, tick);
                                    track.write(data, 0, data.length);
                                    Log.e("DEBUG", "Length " + data.length);
                                }
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    });
                    thrd.start();
                }
            });

Моя decode() -функция (на основе этот урок ) с JLayer 1.0.1:

public static byte[] decode(String path, int startMs, int maxMs)
            throws IOException {
        ByteArrayOutputStream outStream = new ByteArrayOutputStream(1024);

        float totalMs = 0;
        boolean seeking = true;

        File file = new File(path);
        InputStream inputStream = new BufferedInputStream(new FileInputStream(file), 8 * 1024);
        try {
            Bitstream bitstream = new Bitstream(inputStream);
            Decoder decoder = new Decoder();

            boolean done = false;
            while (! done) {
                Header frameHeader = bitstream.readFrame();
                if (frameHeader == null) {
                    done = true;
                } else {
                    totalMs += frameHeader.ms_per_frame();

                    if (totalMs >= startMs) {
                        seeking = false;
                    }

                    if (!seeking) {
                        SampleBuffer output = (SampleBuffer) decoder.decodeFrame(frameHeader, bitstream);

                        if (output.getSampleFrequency() != 44100
                                || output.getChannelCount() != 2) {
                            Log.w("ERROR", "mono or non-44100 MP3 not supported");
                        }

                        short[] pcm = output.getBuffer();
                        for (short s : pcm) {
                            outStream.write(s & 0xff);
                            outStream.write((s >> 8) & 0xff);
                        }
                    }

                    if (totalMs >= (startMs + maxMs)) {
                        done = true;
                    }
                }
                bitstream.closeFrame();
            }
        } catch (BitstreamException e) {
            throw new IOException("Bitstream error: " + e);
        } catch (DecoderException e) {
            Log.w("ERROR", "Decoder error", e);
        } finally {
            inputStream.close();
        }
        return outStream.toByteArray();
    }

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

1 Ответ

0 голосов
/ 20 января 2019

Основная причина этого оказалась: вы использовали функцию decode() таким образом, для которого она не была специально разработана. Даже при том, что decode() позволит вам декодировать любую часть потока .mp3 в режиме произвольного доступа, на практике первые несколько мс возвращенного аудио равны всегда без звука , независимо от того, начинаете ли вы в начале песни или в середине. Это молчание вызывало «пробелы». По-видимому, функция decode() была предназначена для повторного запуска воспроизведения в случайном месте, например, из-за пользовательского «поиска».

decode() ведет себя таким образом, потому что для декодирования N-го блока сжатых данных декодеру необходимы оба блока N-1 и блока N. Декомпрессированные данные, которые соответствуют блоку N, будут хорошо, но данные для блока N-1 будут иметь этот звук "постепенного появления". Это общая особенность декодеров .mp3, и я знаю, что это происходит и с AAC. Между тем, декодирование блоков N + 1, N + 2, N + 3 и т. Д. Не представляет проблем, поскольку в каждом случае декодер уже имеет предыдущий блок.

Одним из решений является изменение функции decode():

private Decoder decoder;
private float totalMs;
private Bitstream bitstream;
private InputStream inputStream;

//call this once, when it is time to start a new song:
private void startNewSong(String path) throws IOException
{
    decoder = new Decoder();
    totalMs = 0;
    File file = new File(path);
    inputStream = new BufferedInputStream(new FileInputStream(file), 8 * 1024);
    bitstream = new Bitstream(inputStream);
}

private byte[] decode(String path, int startMs, int maxMs)
        throws IOException {
    ByteArrayOutputStream outStream = new ByteArrayOutputStream(1024);


    try {
        boolean done = false;
        while (! done) {
            Header frameHeader = bitstream.readFrame();
            if (frameHeader == null) {
                done = true;
                inputStream.close();   //Note this change. Now, the song is done. You can also clean up the decoder here.
            } else {
                totalMs += frameHeader.ms_per_frame();

                SampleBuffer output = (SampleBuffer) decoder.decodeFrame(frameHeader, bitstream);

                if (output.getSampleFrequency() != 44100
                        || output.getChannelCount() != 2) {
                    Log.w("ERROR", "mono or non-44100 MP3 not supported");
                }

                short[] pcm = output.getBuffer();
                for (short s : pcm) {
                    outStream.write(s & 0xff);
                    outStream.write((s >> 8) & 0xff);
                }

                if (totalMs >= (startMs + maxMs)) {
                    done = true;
                }
            }
            bitstream.closeFrame();
        }
    } catch (BitstreamException e) {
        throw new IOException("Bitstream error: " + e);
    } catch (DecoderException e) {
        Log.w("ERROR", "Decoder error", e);
    }
    return outStream.toByteArray();
}

Этот код немного грубоват и готов, и он может использовать некоторые улучшения. Но общий подход, вместо произвольного доступа, decode() использует FSM , чтобы каждый раз декодировать немного больше песни; прочитайте немного больше файла и отправьте еще несколько кусков в декодер. Поскольку состояние декодера (и bitstream) сохраняется между каждым вызовом decode(), нет необходимости искать блок N-1.

UDP и потоковая передача

Правильность вашего подхода UDP зависит от многих вещей. Вы можете искать другие вопросы, которые касаются именно этого. UDP удобен для широковещательной рассылки на несколько устройств в данной подсети, но он не поможет вам гарантировать, что пакеты получены по порядку или вообще. Вы можете хотеть TCP вместо этого. Также подумайте, хотите ли вы передать закодированные блоки .mp3 (возвращаемые bitstream.readFrame()) или блоки распакованного звука. Также подумайте о том, как вы будете справляться с задержкой в ​​сети, разрывом соединений и буферизацией. Здесь есть много сложных дизайнерских решений, и у каждого выбора есть свои плюсы и минусы. Удачи.

...