首页 > 解决方案 > MediaCodec和MediaExtractor的理解

问题描述

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

  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::readSampleDataMediaExtractor::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();
}

也欢迎快速代码审查。

标签: javaandroidaudioandroid-mediacodec

解决方案


  1. 我完全确定它为什么会这样编码,但我认为小的差异在预期的公差范围内。请记住,mp3 是一种有损编解码器,只要声音表示足够接近,解码器的输出值就不会与输入相同。但这并不能说明为什么这两个渠道最终会略有不同。

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

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


推荐阅读