MediaCodec和MediaExtractor的理解

Understanding of MediaCodec and MediaExtractor

我想对音频文件做一些处理而不播放它们,只是数学。我怀疑我是否做对了并且有几个问题。我读了一些例子,但其中大部分是关于视频流的,根本没有处理原始数据。

  1. 我准备了一个mp3文件,有2个相同的声道,即立体声,但左右声道相同。解码后,我希望获得具有相同数字对的缓冲区,因为 PCM-16 交替存储通道样本,如 {L R L R L R...},对吧?例如:

    {105105601601-243-243-484-484...} .

    但是我得到了一对接近但不相等的数字:

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

    mp3 算法对相同值的编码是否不同或为什么?

  2. 我想以 1024 个样本为一组处理数据。如果没有足够的样本用于另一包,我想将其余的保存到下一批原始数据(参见代码中的 mExcess)。是否保证订单会被保留?

  3. 我曾经把"sample"理解为音频数据的每一个值。在这里我看到了 MediaExtractor::readSampleDataMediaExtractor::advance 方法。第一个returns~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 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. 我完全确定为什么要这样编码,但我认为小差异在预期的公差范围内。请记住,mp3 是一种有损编解码器,解码器的输出值不会与输入值相同,只要声音表示足够接近即可。但这并不能说明为什么这两个渠道最终会有细微的不同。

  2. 是的,解码帧的各个顺序将是相同的。确切的值不匹配,但它的声音应该相似。

  3. 在 MediaExtractor 中,样本是一个编码数据包,您应该将其提供给解码器。对于 mp3,这通常是 1152 个样本(每个通道)。