MediaCodec和MediaExtractor的理解

5

我希望对音频文件进行处理,但不播放,只做数学运算。我怀疑自己的方法是否正确,并有几个问题。我阅读了一些示例,但大多数都是关于视频流的,根本没有使用原始数据。

  1. 我准备了一个具有2个相同通道的mp3文件,即立体声,但左右声道相同。解码后,我希望获得一对相等数字的缓冲区,因为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. 我曾经理解“sample”是音频数据的每个单一值。在这里,我看到了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();
}

欢迎进行快速代码审查。

1个回答

3
  1. 我不确定为什么会以这种方式编码,但我认为这种小变化在预期的容差范围内。请记住,mp3是一种有损编解码器,解码器的输出值与输入值不同,只要听觉表现足够接近即可。但这并不能说明为什么两个声道会略有不同。

    "最初的回答"翻译成中文是“原始答案”

  2. 是的,解码后每帧的顺序将保持一致。确切的数值可能不匹配,但听起来应该是相似的。

    "最初的回答"翻译成中文是“原始答案”

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

    "最初的回答"翻译成中文是“原始答案”


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接