Понимание MediaCodec и MediaExtractor

197
23 января 2021, 23:00

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

  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 сэмплов. Если для очередной пачки будет недостаточно данных, я хочу сохранить остаток до следующей пачки сырых данных. Есть ли гарантия, что порядок при этом будет сохранён?

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

Вот код:

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 k = 0;
                    k + SAMPLES_PER_CHUNK * channelCount < buf.length;
                    k += 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 offset = 0;
                                offset + SAMPLES_PER_CHUNK < buf.length;
                                offset += SAMPLES_PER_CHUNK) {
                            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 offset = 0;
                                offset + SAMPLES_PER_CHUNK * 2 < buf.length;
                                offset += SAMPLES_PER_CHUNK * 2) {
                            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();
}

Быстрый code review также приветствуется.

READ ALSO
передача переменной name при наследовании

передача переменной name при наследовании

не пойму как присобачить "Полкана" к "большому псу" сохраняя вызов конструктора предкав остальном всё работает как надо

95
Аналог await Task.Delay(1000) в JavaFX

Аналог await Task.Delay(1000) в JavaFX

господаЕсть такое задание: разработать программу, изображающую на экране работающие электронные часы с цифровым индикатором, а также с индикацией...

120
Mozilla декодирует url в адресной строке

Mozilla декодирует url в адресной строке

Mozilla декодирует закодированный url в адресной строкеИз-за чего получаю bad request, т

82
Настройка компилятора TypeScript

Настройка компилятора TypeScript

Есть ли способ заставить Классы в TypeScript компилироваться в Фабричные Функции, где приватные свойства и методы не будут попадать в прототип?...

119