【问题标题】:Unable to transcode audio via Android MediaCodec API无法通过 Android MediaCodec API 对音频进行转码
【发布时间】:2015-09-12 10:15:45
【问题描述】:

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

我不确定为什么网上没有多少关于此的可用资源,可能是由于相关的复杂性。虽然,我已经设法了解基本方法应该是:

通过MediaExtractor获取样本数据 -> Enqueue decoder input buffer -> Dequeue output buffer并获取解码后的数据 -> Enqueue encoder input buffer -> Dequeue encoder output buffer -> 将编码后的数据写入文件。

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.解码只要track就是,为什么要考虑实际的采样间隔?

编辑 3:使用 AudioTrack.write() 进行测试,音频播放得很好,但这不是故意的,并且表明解码与正在馈送的媒体文件同步进行,这应该尽快发生可以让编码器快速完成工作。更改decoder.queueInputBuffer() 中的presentationTimeUs 什么也没做。

【问题讨论】:

    标签: java android android-mediacodec mp4parser


    【解决方案1】:

    您的方法是正确的,缺少的部分是将编码帧与MediaMuxer 混合到有效的 MP4 文件中。在bigflake 上有一个很好的(也是唯一的)示例。这件事最相关的例子是

    您必须将它们组合并简化/修改以使用音频而不是视频。您需要 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);
    

    【讨论】:

    • 感谢您的反馈。我已经提到这将使用可用的 mp4parser 库来执行,因为 MediaMuxer 只能从 API 18 获得。这里的问题是解码效果不佳,原始 AAC 也应该可以播放。
    • 另外,您不需要中间字节[] 块,将缓冲区从解码器传递到编码器等。这只是一种时间方式。例如:
    • 将 'info.presentationTimeUs' 而不是 'extractor.getSampleTime()' 传递给 'encoder.queueInputBuffer()' 可能是正确的
    • 总的来说,我建议您查看 decode-edit-encode 示例并尽可能接近它编写代码
    • 将尝试您的建议,以及有关如何避免 BufferOverflowException 的任何提示?我不确定如何确定编码器缓冲区的大小。
    猜你喜欢
    • 2020-03-21
    • 1970-01-01
    • 2013-11-18
    • 2016-03-12
    • 2016-04-24
    • 2023-03-14
    • 1970-01-01
    • 2017-09-06
    • 2015-11-09
    相关资源
    最近更新 更多