简体   繁体   中英

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. This is audio only. The function runs successfully and outputs a .mp4 of reasonable that is recognized as AAC-encoded. But it doesn't play on Android, web or iOS players, and crashes 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. Maybe that's the reason players won't play it. Try using some other media type, audio/mp4 or audio/3gpp .

See AAC container formats .

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.
  3. Add code to apply input source property such as channels and sample rate to 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? 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!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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