简体   繁体   English

Android:MediaCodec + MediaMuxer编码的音频MP4无法播放

[英]Android: MediaCodec+MediaMuxer encoded audio MP4 won't play

I have the following function which takes a WAV (PCM) file and encodes it to an AAC-encoded MP4 file using Android's MediaCode and MediaMuxer classes. 我有以下函数,该函数需要一个WAV(PCM)文件,并使用Android的MediaCode和MediaMuxer类将其编码为AAC编码的MP4文件。 This is audio only. 这仅是音频。 The function runs successfully and outputs a .mp4 of reasonable that is recognized as AAC-encoded. 该函数成功运行,并输出一个公认的AAC编码的.mp4。 But it doesn't play on Android, web or iOS players, and crashes Audacity. 但是它不能在Android,Web或iOS播放器上播放,并且会使Audacity崩溃。 Is there something I am missing? 我有什么想念的吗? Code is shown below. 代码如下所示。

public void encode(final String from, final String to, final Callback callback) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                extractor.setDataSource(from);
                int numTracks = extractor.getTrackCount();
                for (int i = 0; i < numTracks; ++i) {
                    MediaFormat format = extractor.getTrackFormat(i);
                    String mime = format.getString(MediaFormat.KEY_MIME);
                    Log.d(TAG, "Track " + i + " mime-type: " + mime);
                    if (true) {
                        extractor.selectTrack(i);
                    }
                }

                MediaCodec codec = MediaCodec.createEncoderByType("audio/mp4a-latm");
                MediaFormat format = new MediaFormat();
                format.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
                format.setInteger(MediaFormat.KEY_BIT_RATE, 128 * 1024);
                format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 2);
                format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 44100);
                format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
                codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
                final MediaMuxer muxer = new MediaMuxer(to, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
                final ByteBuffer byteBuffer = ByteBuffer.allocate(65536);
                codec.setCallback(new MediaCodec.Callback() {
                    @Override
                    public void onInputBufferAvailable(MediaCodec codec, int bufferIndex) {
                        ByteBuffer inputBuffer = codec.getInputBuffer(bufferIndex);
                        if (isEndOfStream) {
                            return;
                        }
                        int sampleCapacity = inputBuffer.capacity() / 8;
                        if (numAvailable == 0) {
                            numAvailable = extractor.readSampleData(byteBuffer, 0);
                            if (numAvailable <= 0) {
                                codec.queueInputBuffer(bufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                                isEndOfStream = true;
                                return;
                            }
                            long presentationTimeUs = extractor.getSampleTime();
                            extractor.advance();
                        }
                        if (numAvailable < sampleCapacity) {
                            codec.queueInputBuffer(bufferIndex, 0, numAvailable * 8, 0, 0);
                            numAvailable = 0;
                        } else {
                            codec.queueInputBuffer(bufferIndex, 0, sampleCapacity * 8, 0, 0);
                            numAvailable -= sampleCapacity;
                        }
                    }

                    @Override
                    public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) {
                        ByteBuffer outputBuffer = codec.getOutputBuffer(index);
                        muxer.writeSampleData(audioTrackIndex,outputBuffer,info);
                        codec.releaseOutputBuffer(index, true);
                        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                            Log.d(TAG, "end of encoding!");
                            codec.stop();
                            codec.release();
                            extractor.release();
                            extractor = null;
                            muxer.stop();
                            muxer.release();
                            callback.run(true);
                        }
                    }

                    @Override
                    public void onError(MediaCodec codec, MediaCodec.CodecException e) {
                        Log.e(TAG, "codec error", e);

                    }

                    @Override
                    public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
                        audioTrackIndex = muxer.addTrack(format);
                        muxer.start();

                    }
                });
                codec.start();
            } catch (IOException e) {
                Log.e(TAG,"Unable to encode",e);
                callback.run(false);
            }

        }
    }).run();

You seem to be encoding your AAC into LATM format which isn't very popular. 您似乎将AAC编码为LATM 格式 ,这种格式不太流行。 Maybe that's the reason players won't play it. 也许这就是玩家不玩的原因。 Try using some other media type, audio/mp4 or audio/3gpp . 尝试使用其他媒体类型, audio/mp4audio/3gpp

See AAC container formats . 请参阅AAC容器格式

You need to: 你需要:

  1. Add timestamp information correctly since media muxer need to use it to TAG packet time information. 由于媒体复用器需要使用它来标记数据包时间信息,因此请正确添加时间戳信息。
  2. Add logic to copy data buffer from extractor data buffer (PCM) to mediacodec input buffer, only refer to buffer index will only encode a random data buffer without initial. 添加逻辑以将数据缓冲区从提取器数据缓冲区(PCM)复制到媒体编解码器输入缓冲区,仅引用缓冲区索引将仅对随机数据缓冲区进行编码而无需初始。
  3. Add code to apply input source property such as channels and sample rate to mediacodec. 添加代码以将输入源属性(例如频道和采样率)应用于mediacodec。 Not sure if you are intent to encode with different channels and sample rate! 不知道您是否打算使用不同的通道和采样率进行编码!

Example code as below: 示例代码如下:

MediaExtractor extractor = null;
int numAvailable = 0;
boolean isEndOfStream = false;
int audioTrackIndex = 0;
long totalen = 0;
int channels = 0;
int sampleRate = 0;
public void encode(final String from, final String to) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                extractor = new MediaExtractor();
                extractor.setDataSource(from);
                int numTracks = extractor.getTrackCount();
                for (int i = 0; i < numTracks; ++i) {
                    MediaFormat format = extractor.getTrackFormat(i);
                    String mime = format.getString(MediaFormat.KEY_MIME);
                    Log.d(TAG, "Track " + i + " mime-type: " + mime);
                    if (true) {
                        extractor.selectTrack(i);
                        channels = extractor.getTrackFormat(i).getInteger(MediaFormat.KEY_CHANNEL_COUNT);
                        sampleRate = extractor.getTrackFormat(i).getInteger(MediaFormat.KEY_SAMPLE_RATE);
                        Log.e(TAG,"sampleRate:" + sampleRate + " channels:" + channels);
                    }
                }
                String mimeType = "audio/mp4a-latm";
                MediaCodec codec = MediaCodec.createEncoderByType(mimeType);
                MediaFormat format = new MediaFormat();
                format.setString(MediaFormat.KEY_MIME, mimeType);
                format.setInteger(MediaFormat.KEY_BIT_RATE, 128 * 1024);
                format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, channels);
                format.setInteger(MediaFormat.KEY_SAMPLE_RATE, sampleRate);
                format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
                codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
                final MediaMuxer muxer = new MediaMuxer(to, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
                final ByteBuffer byteBuffer = ByteBuffer.allocate(65536);
                codec.setCallback(new MediaCodec.Callback() {
                    @Override
                    public void onInputBufferAvailable(MediaCodec codec, int bufferIndex) {
                        ByteBuffer inputBuffer = codec.getInputBuffer(bufferIndex);
                        inputBuffer.clear();
                        if (isEndOfStream) {
                            return;
                        }
                        int sampleCapacity = inputBuffer.capacity();
                        if (numAvailable == 0) {
                            numAvailable = extractor.readSampleData(byteBuffer, 0);
                            if (numAvailable <= 0) {
                                codec.queueInputBuffer(bufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                                isEndOfStream = true;
                                return;
                            }
                            extractor.advance();
                        }
                        long timestampUs = 1000000l * totalen / (2 * channels * sampleRate);
                        if (numAvailable < sampleCapacity) {
                            byte[] byteArray = new byte[numAvailable];
                            byteBuffer.get(byteArray);
                            inputBuffer.put(byteArray, 0, (int)numAvailable);
                            totalen += numAvailable;
                            codec.queueInputBuffer(bufferIndex, 0, numAvailable, timestampUs, 0);
                            numAvailable = 0;
                        } else {
                            byte[] byteArray = new byte[sampleCapacity];
                            byteBuffer.get(byteArray);
                            inputBuffer.put(byteArray, 0, (int)sampleCapacity);
                            totalen += sampleCapacity;
                            codec.queueInputBuffer(bufferIndex, 0, sampleCapacity, timestampUs, 0);
                            numAvailable -= sampleCapacity;
                        }
                    }
                    @Override
                    public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) {
                        ByteBuffer outputBuffer = codec.getOutputBuffer(index);
                        muxer.writeSampleData(audioTrackIndex,outputBuffer,info);
                        codec.releaseOutputBuffer(index, true);
                        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                            Log.d(TAG, "end of encoding!");
                            codec.stop();
                            codec.release();
                            extractor.release();
                            extractor = null;
                            muxer.stop();
                            muxer.release();
                            //callback.run(true);
                        }
                    }
                    @Override
                    public void onError(MediaCodec codec, MediaCodec.CodecException e) {
                        Log.e(TAG, "codec error", e);

                    }
                    @Override
                    public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
                        audioTrackIndex = muxer.addTrack(format);
                        muxer.start();
                    }
                });
                codec.start();
            } catch (IOException e) {
                Log.e(TAG,"Unable to encode",e);
                //callback.run(false);
            }
        }
    }).run();
}

BTW,Why you need to divide 8 with the buffer length? 顺便说一句,为什么您需要将8除以缓冲区长度? And what's the Callback class? 什么是回调类? Please share the import module! 请共享导入模块! I almost can not pass build with the callback parameter so comment it out! 我几乎无法通过带有回调参数的构建,因此将其注释掉!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM