Понимание MediaCodec и MediaExtractor - PullRequest
3 голосов
/ 04 июня 2019

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

  1. Я подготовил mp3-файл, который имеет 2 одинаковых канала, т.е. он является стереофоническимно левый и правый одинаковы.После декодирования я ожидал получить буфер с парами равных чисел, потому что PCM-16 поочередно хранит выборки каналов, например, {L R L R L R ...}, верно?Например:

    {105 105 601 601 -243 -243 -484 -484 ...}.

    Но я получаю пары близких чисел, но не равных:

    {-308 -264 -1628 -1667 -2568 -2550 -4396 -4389}

    Алгоритмы mp3 по-разному кодируют одни и те же значения или почему?

  2. Я хочу обрабатывать данные в пакетах по 1024 семпла.Если для другого пакета будет недостаточно образцов, я хочу сохранить остальные до следующей партии необработанных данных (см. mExcess в коде).Есть ли гарантия, что порядок будет сохранен?

  3. Я привык понимать «выборку» как каждое значение аудиоданных.Здесь я вижу MediaExtractor::readSampleData и MediaExtractor::advance методы.Первое возвращает ~ 2000 значений, в описании второго сказано «Переход к следующему образцу».Это просто совпадение имен?Я видел пару примеров, где эти методы вызываются попарно в цикле.Является ли мое использование правильным?

Вот мой код:

public static void foo(String filepath) throws IOException {
    final int SAMPLES_PER_CHUNK = 1024;

    MediaExtractor mediaExtractor = new MediaExtractor();
    mediaExtractor.setDataSource(filepath);
    MediaFormat mediaFormat = mediaExtractor.getTrackFormat(0);
    mediaExtractor.release();

    MediaCodecList mediaCodecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
    mediaFormat.setString(MediaFormat.KEY_FRAME_RATE, null);
    String codecName = mediaCodecList.findDecoderForFormat(mediaFormat);
    mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 0);  // MediaCodec crashes with JNI
                                                            // error if FRAME_RATE is null
    MediaCodec mediaCodec = MediaCodec.createByCodecName(codecName);
    mediaCodec.setCallback(new MediaCodec.Callback() {
        private MediaExtractor mExtractor;
        private short[] mExcess;

        @Override
        public void onInputBufferAvailable(MediaCodec codec, int index) {
            if (mExtractor == null) {
                mExtractor = new MediaExtractor();
                try {
                    mExtractor.setDataSource(filepath);
                    mExtractor.selectTrack(0);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                mExcess = new short[0];
            }
            ByteBuffer in = codec.getInputBuffer(index);
            in.clear();
            int sampleSize = mExtractor.readSampleData(in, 0);
            if (sampleSize > 0) {
                boolean isOver = !mExtractor.advance();
                codec.queueInputBuffer(
                        index,
                        0,
                        sampleSize,
                        mExtractor.getSampleTime(),
                        isOver ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
            } else {
                int helloAmaBreakpoint = 1;
            }
        }

        @Override
        public void onOutputBufferAvailable(
                MediaCodec codec,
                int index,
                MediaCodec.BufferInfo info) {
            ByteBuffer tmp = codec.getOutputBuffer(index);
            if (tmp.limit() == 0) return;

            ShortBuffer out = tmp.order(ByteOrder.nativeOrder()).asShortBuffer();
            // Prepend the remainder from previous batch to the new data
            short[] buf = new short[mExcess.length + out.limit()];
            System.arraycopy(mExcess, 0, buf, 0, mExcess.length);
            out.get(buf, mExcess.length, out.limit());

            final int channelCount
                    = codec.getOutputFormat().getInteger(MediaFormat.KEY_CHANNEL_COUNT);
            for (
                    int offset  = 0;
                    offset + SAMPLES_PER_CHUNK * channelCount < buf.length;
                    offset += SAMPLES_PER_CHUNK * channelCount) {

                double[] x = new double[SAMPLES_PER_CHUNK];  // left channel
                double[] y = new double[SAMPLES_PER_CHUNK];  // right channel
                switch (channelCount) {
                    case 1:  // if 1 channel then make 2 identical arrays
                        for (int i = 0; i < SAMPLES_PER_CHUNK; ++i) {
                            x[i] = (double) buf[offset + i];
                            y[i] = (double) buf[offset + i];
                        }
                        break;
                    case 2:  // if 2 channels then read values alternately
                        for (int i = 0; i < SAMPLES_PER_CHUNK; ++i) {
                            x[i] = (double) buf[offset + i * 2];
                            y[i] = (double) buf[offset + i * 2 + 1];
                        }
                        break;
                    default:
                        throw new IllegalStateException("No algorithm for " + channelCount + " channels");
                }

                /// ... some processing ... ///
            }

            // Save the rest until next batch of raw data
            int samplesLeft = buf.length % (SAMPLES_PER_CHUNK * channelCount);
            mExcess = new short[samplesLeft];
            System.arraycopy(
                    buf,
                    buf.length - samplesLeft,
                    mExcess,
                    0,
                    samplesLeft);

            codec.releaseOutputBuffer(index, false);
            if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) > 0) {
                codec.stop();
                codec.release();
                mExtractor.release();
            }
        }

        @Override
        public void onError(MediaCodec codec, MediaCodec.CodecException e) {

        }

        @Override
        public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {

        }
    });

    mediaFormat.setInteger(MediaFormat.KEY_PCM_ENCODING, AudioFormat.ENCODING_PCM_16BIT);
    mediaCodec.configure(mediaFormat, null, null, 0);
    mediaCodec.start();
}

Быстрый просмотр кода также приветствуется.

1 Ответ

0 голосов
/ 06 июня 2019
  1. Я точно уверен, почему он будет кодировать их таким образом, но я думаю, что небольшое отклонение находится в пределах ожидаемого допуска. Имейте в виду, что mp3, являющийся кодеком с потерями, выходные значения декодера не будут совпадать с входными, если звуковое представление достаточно близко. Но это не указывает на то, почему два канала будут немного различаться.

  2. Да, индивидуальный порядок декодированных кадров будет таким же. Точные значения не будут совпадать, но звук должен быть похожим.

  3. В MediaExtractor выборка представляет собой один закодированный пакет данных, который вы должны подать в декодер. Для mp3 это обычно 1152 сэмпла (на канал).

...