当前位置: 首页 > 知识库问答 >
问题:

无法通过Android MediaCodec API转码音频

董哲
2023-03-14

我正在尝试写一个基本的原始AAC数据到一个文件,希望我可以使用mp4parser封装它与一个视频轨道。为此,我需要将任何给定的音频文件编码为该格式。MediaCodec API从API16开始就很容易获得,所以我决定使用它来进行编解码操作。

我不确定为什么没有很多资源可以在线上关于这一点,可能是由于相关的复杂性。不过,我已经了解到基本的方法应该是:

通过MediaExtractor获取样本数据->编码器输入缓冲区入列->输出缓冲区出列并获取解码数据->编码器输入缓冲区入列->编码器输出缓冲区出列->将编码数据写入文件。

private void transcodeFile(File source, File destination) throws IOException {
    FileInputStream inputStream = new FileInputStream(source);
    FileOutputStream outputStream = new FileOutputStream(destination);

    log("Transcoding file: " + source.getName());

    MediaExtractor extractor;
    MediaCodec encoder;
    MediaCodec decoder;

    ByteBuffer[] encoderInputBuffers;
    ByteBuffer[] encoderOutputBuffers;
    ByteBuffer[] decoderInputBuffers;
    ByteBuffer[] decoderOutputBuffers;

    int noOutputCounter = 0;
    int noOutputCounterLimit = 10;

    extractor = new MediaExtractor();
    extractor.setDataSource(inputStream.getFD());
    extractor.selectTrack(0);

    log(String.format("TRACKS #: %d", extractor.getTrackCount()));
    MediaFormat format = extractor.getTrackFormat(0);
    String mime = format.getString(MediaFormat.KEY_MIME);
    log(String.format("MIME TYPE: %s", mime));


    final String outputType = MediaFormat.MIMETYPE_AUDIO_AAC;
    encoder = MediaCodec.createEncoderByType(outputType);
    MediaFormat encFormat = MediaFormat.createAudioFormat(outputType, 44100, 2);
    encFormat.setInteger(MediaFormat.KEY_BIT_RATE, 64000);
    encoder.configure(encFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);

    decoder = MediaCodec.createDecoderByType(mime);
    decoder.configure(format, null, null, 0);

    encoder.start();
    decoder.start();

    encoderInputBuffers = encoder.getInputBuffers();
    encoderOutputBuffers = encoder.getOutputBuffers();

    decoderInputBuffers = decoder.getInputBuffers();
    decoderOutputBuffers = decoder.getOutputBuffers();

    int timeOutUs = 1000;
    long presentationTimeUs = 0;

    MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
    boolean inputEOS = false;
    boolean outputEOS = false;

    while(!outputEOS && noOutputCounter < noOutputCounterLimit) {
        noOutputCounter++;

        if(!inputEOS) {
            int decInputBufferIndex = decoder.dequeueInputBuffer(timeOutUs);
            log("decInputBufferIndex: " + decInputBufferIndex);
            if (decInputBufferIndex >= 0) {
                ByteBuffer dstBuffer = decoderInputBuffers[decInputBufferIndex];

                //Getting sample with MediaExtractor
                int sampleSize = extractor.readSampleData(dstBuffer, 0);
                if (sampleSize < 0) {
                    inputEOS = true;
                    log("Input EOS");
                    sampleSize = 0;
                } else {
                    presentationTimeUs = extractor.getSampleTime();
                }

                log("Input sample size: " + sampleSize);

                //Enqueue decoder input buffer
                decoder.queueInputBuffer(decInputBufferIndex, 0, sampleSize, presentationTimeUs, inputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
                if (!inputEOS) extractor.advance();

            } else {
                log("decInputBufferIndex: " + decInputBufferIndex);
            }
        }

        //Dequeue decoder output buffer
        int res = decoder.dequeueOutputBuffer(info, timeOutUs);
        if(res >= 0) {
            if(info.size > 0) noOutputCounter = 0;

            int decOutputBufferIndex = res;
            log("decOutputBufferIndex: " + decOutputBufferIndex);

            ByteBuffer buffer = decoderOutputBuffers[decOutputBufferIndex];
            buffer.position(info.offset);
            buffer.limit(info.offset + info.size);

            final int size = buffer.limit();
            if(size > 0) {
                //audioTrack.write(buffer, buffer.limit(), AudioTrack.MODE_STATIC);

                int encInputBufferIndex = encoder.dequeueInputBuffer(-1);
                log("encInputBufferIndex: " + encInputBufferIndex);
                //fill the input buffer with the decoded data
                if(encInputBufferIndex >= 0) {
                    ByteBuffer dstBuffer = encoderInputBuffers[encInputBufferIndex];
                    dstBuffer.clear();
                    dstBuffer.put(buffer);

                    encoder.queueInputBuffer(encInputBufferIndex, 0, info.size, info.presentationTimeUs, 0);
                    int encOutputBufferIndex = encoder.dequeueOutputBuffer(info, timeOutUs);
                    if(encOutputBufferIndex >= 0) {
                        log("encOutputBufferIndex: " + encOutputBufferIndex);
                        ByteBuffer outBuffer = encoderOutputBuffers[encOutputBufferIndex];
                        byte[] out = new byte[outBuffer.remaining()];
                        outBuffer.get(out);
                        //write data to file
                        outputStream.write(out);
                    }
                }
            }
            decoder.releaseOutputBuffer(decOutputBufferIndex, false);
            if((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                outputEOS = true;
                log("Output EOS");
            }
        } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
            decoderOutputBuffers = decoder.getOutputBuffers();
            log("Output buffers changed.");
        } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
            log("Output format changed.");
        } else {
            log("Dequeued output buffer returned: " + res);
        }
    }

    log("Stopping..");
    releaseCodec(decoder);
    releaseCodec(encoder);
    inputStream.close();
    outputStream.close();

}

编辑:设法修复了一个异常,问题仍然存在。

编辑2:我已经通过将缓冲区大小设置为编码器格式设置中的比特率来防止缓冲区溢出。目前存在两个问题:1。在很短的间隔之后,它就会卡在这里,可能会无限期地等待。int encInputBufferIndex=dequeueInputBuffer(-1);2。译码的时间和轨道一样长,为什么这会考虑样本的实际间隔呢?

编辑3:使用audiotrack.write()进行测试,音频播放得很好,但这并不是有意的,它暗示解码是与被馈送的媒体文件同步进行的,这应该尽可能快地进行,以允许编码器快速地完成它的工作。更改Decoder.QueueInputBuffer()中的presentationTimeUs没有执行任何操作。

共有1个答案

岳嘉容
2023-03-14

你是在正确的方式,缺少的部分是复用编码帧到有效的MP4文件与MediaMuxer。在Bigflake上有一个很好的(也是唯一的)例子。这方面最相关的例子有

  • EncodeAndMuxTest.java
  • DecodeEditEncodeTest.java

您必须组合、简化/修改它们,以便使用音频而不是视频。您需要API 18来实现上述功能

int decoderStatus = audioDecoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
  if (decoderStatus >= 0) {
      // no output available yet
      if (VERBOSE) Log.d(TAG, "no output from audio decoder available");
...
   } else if (decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
            audioDecoderOutputBuffers = audioDecoder.getOutputBuffers();
            if (VERBOSE) Log.d(TAG, "decoder output buffers changed (we don't care)");
    } else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
            // expected before first buffer of data
            if (VERBOSE) {
                    MediaFormat newFormat = audioDecoder.getOutputFormat();
                    Log.d(TAG, "decoder output format changed: " + newFormat);
                }
    } else if (decoderStatus < 0) {
            Log.e(TAG, "unexpected result from decoder.dequeueOutputBuffer: "+decoderStatus);
            throw new RuntimeException("Issue with dencoding audio");
    } else { // decoderStatus >= 0
            if (VERBOSE) Log.d(TAG, "audio decoder produced buffer "
                                + decoderStatus + " (size=" + info.size + ")");

            if (info.size! = 0) {                           
                // Forward decoder buffer to encoder
                ByteBuffer decodedData = audioDecoderOutputBuffers[decoderStatus];
                decodedData.position(info.offset);
                decodedData.limit(info.offset + info.size);

                 // Possibly edit buffer data

                // Send it to the audio encoder.
                int encoderStatus = audioEncoder.dequeueInputBuffer(-1);
                if (encoderStatus < 0) {
                    throw new RuntimeException("Could not get input buffer for audio encoder!!!");
                }
            audioEncoderInputBuffers[encoderStatus].clear();
            audioEncoderInputBuffers[encoderStatus].put(decodedData);
         }
audioEncoder.queueInputBuffer(encoderStatus, 0, info.size, mAudioMediaTime, 0);
     if (VERBOSE) Log.d(TAG, "Submitted to AUDIO encoder frame, size=" + info.size + " time=" + mAudioMediaTime);
    }
 audioDecoder.releaseOutputBuffer(decoderStatus, false);
 类似资料:
  • 问题内容: 以下是我运行时遇到的错误: 问题答案: 在没有空格的路径中创建您的virtualenv环境。这就是为什么它发生的原因: 创建环境时,它会建立一个目录。在该目录中是与环境有关的所有可执行文件。有些是脚本。如您所知,hashbang用来告诉系统使用什么解释程序来运行脚本。您可能经常在脚本顶部看到此信息: 如果脚本位于,则告诉系统运行以下命令来执行脚本: 就您而言,virtualenv正在创

  • 从插座上看。io网站 从1.0开始,可以来回发送任何Blob:图像、音频、视频。 我现在想知道,这是否能解决我最近想要实现的目标。 我其实是在寻找一种方法,如何直播音频流从(A-即,麦克风输入...)到所有客户连接到我的网站。像这样的事情可能吗?我一直在摆弄WebRTC(https://www.webrtc-experiment.com/)示例,但我无法为几个连接的客户端管理目标。 我的想法是关于

  • 问题内容: 这是我的客户代码 这是服务器代码 但是即使我这样修改它,我也不会在fromClient中接收任何内容 我没有结果 问题答案: 客户端需要调用或导致将缓冲的数据推送到服务器。(如果您打算写入更多数据,请刷新,否则关闭。) 服务器端需要在实例上使用readInt读取数据。使用DataOutputStream写入的数据应使用DataInputStream读取。 最后,摆脱BufferedRe

  • 我正试图创建一个呼叫录音机应用程序,然而,我的音频来源时,它的麦克风工作正常,但它不能捕捉第二个声音。 如果我更改为VOICE_CALL,我的应用程序关闭。 我在PlayStore上看到了数百个类似的应用程序,并想知道它们能够记录通话双方的秘密是什么。

  • 一边录音,一边将录制成的 wav 格式音频文件转码成 amr 音频格式。只支持真机运行调试。此代码在之前代码(http://code4app.com/ios/录音并AMR和WAV互转/51565c3e6803fa5c76000005)基础上改进。 作者说:在开发者@ Jeans_黄 上传的代码的基础上,主要是对amrFileCoder进行了修改,让代码执行时,可以实现录制音频的同时,对文件流进行转

  • 每当我从我的调用类时,我在行中得到 我知道这是最容易解决的例外,但我已经挣扎了一段时间,我需要帮助…任何人都可以给我一个提示,我错过了什么?