无法通过Android MediaCodec API

时间:2015-06-25 17:09:39

标签: java android mediacodec mp4parser

我试图将基本的原始AAC数据写入文件,希望我可以使用mp4parser将其与视频轨道封装在一起。为此,我需要将任何给定的音频文件编码为该格式。自API 16以来,MediaCodec API随时可用,因此我决定将其用于编解码器操作。

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

通过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:我通过在编码器格式设置中将缓冲区大小设置为比特率来防止缓冲区溢出。目前有两个问题:  在很短的时间间隔之后,它会被困在这里,可能无限期地等待。int encInputBufferIndex = dequeueInputBuffer(-1);  2.解码与轨道一样长,为什么这会考虑样本的实际间隔?

编辑3:使用AudioTrack.write()进行测试,音频播放效果不错,但这并非预期,并且建议解码与正在馈送的媒体文件同步进行,这应该发生在尽可能快地让编码器快速完成工作。更改decoder.queueInputBuffer()中的presentationTimeUs什么也没做。

1 个答案:

答案 0 :(得分:2)

You are on the right way, the missing part is muxing the encoded frames into valid MP4 file with MediaMuxer. There is a good (and only) example for that on bigflake. Most relevant examples for this matter are EncodeAndMuxTest.java DecodeEditEncodeTest.java You'll have to combine and simplify/modify them to work with audio instead of video. You'll need API 18 for the above Edit: how I forward decoder buffer to encoder (more or less). I did not experience buffer overflows so far just hoping that sane implementation will have encoder and decoder buffers of the same capacity: 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);