Android 中使用 MediaCodec 进行编码和解码的音频文件转换

问题描述 投票:0回答:1

我正在尝试将任何音频文件转换为 AAC、128kbps 比特率、32000Hz 采样率。

我期待一个具有上述规格的转换后的音频文件。

我得到了一个输出文件,但它是垃圾音频,ExoPlayer 中存在大量

Audio Sink Error
错误,Codec2Client 中存在大量
query -- param skipped
警告。

这是我的代码:

info -

outputFile
sourceUri
分别属于
AtomicReference<File>
AtomicReference<Uri>
类型

info - 我假设

sourceUri
是单个音频文件的 uri

我先尝试只编码:

startEncoding.setOnClickListener(v ->
        {
            try {
                outputFile.set(File.createTempFile("temp", null));

                MediaExtractor extractor = new MediaExtractor();
                extractor.setDataSource(this, sourceUri.get(), null);
                extractor.selectTrack(0);

                MediaFormat outputFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 32000, 2);
                outputFormat.setInteger(MediaFormat.KEY_BIT_RATE, 128000);


                MediaMuxer muxer = new MediaMuxer(outputFile.get().getPath(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
                int trackIndex = muxer.addTrack(outputFormat);

                MediaCodec.Callback encoderCallbacks = new MediaCodec.Callback() {
                    int sampleSize;
                    long sampleTime;
                    boolean EOSFlag = false;
                    boolean muxerStarted = false;
                    @Override
                    public void onInputBufferAvailable(@NonNull MediaCodec encoder, int index) {
                        Log.d(Tag.DEBUG.toString(), "onInputBufferAvailable: encoding...");
                        if(EOSFlag){
                            encoder.queueInputBuffer(index, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                            return;
                        }
                        ByteBuffer inputBuffer = encoder.getInputBuffer(index);
                        sampleSize = extractor.readSampleData(inputBuffer, 0);
                        if(sampleSize >= 0){
                            sampleTime = extractor.getSampleTime();
                            encoder.queueInputBuffer(index, 0, sampleSize, sampleTime, 0);
                            Log.d(Tag.DEBUG.toString(), "onInputBufferAvailable: decoding...");
                        } else {
                            EOSFlag = true;
                            encoder.queueInputBuffer(index, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                            extractor.release();
                            Log.d(Tag.DEBUG.toString(), "onInputBufferAvailable: decoding completed by extractor.getSampleSize() < 0");
                            return;
                        }
                        if(!extractor.advance()){
                            EOSFlag = true;
                            Log.d(Tag.DEBUG.toString(), "onInputBufferAvailable: decoding completed by extractor.advance() == false");
                            extractor.release();
                        }
                    }

                    @Override
                    public void onOutputBufferAvailable(@NonNull MediaCodec encoder, int index, @NonNull MediaCodec.BufferInfo info) {
                        if(!muxerStarted){
                            muxer.start();
                            muxerStarted = true;
                        }
                        if (info.flags != MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
                            muxer.writeSampleData(trackIndex, encoder.getOutputBuffer(index), info);
                            Log.d(Tag.DEBUG.toString(), "onOutputBufferAvailable: writing to file...");
                            encoder.releaseOutputBuffer(index, false);
                        } else {
                            Log.d(Tag.DEBUG.toString(), "onOutputBufferAvailable: Writing completed.");
                            encoder.stop();
                            encoder.release();
                            muxer.stop();
                            muxer.release();
                            muxerStarted = false;
                            Log.d(Tag.DEBUG.toString(), "onOutputBufferAvailable: Resources released");
                            Log.d(Tag.DEBUG.toString(),
                                    "Source file size: " + new File(sourceUri.get().getPath()).length() +
                                            "\nTarget file size: " + outputFile.get().length());
                        }
                    }

                    @Override
                    public void onError(@NonNull MediaCodec encoder, @NonNull MediaCodec.CodecException e) {
                        encoder.stop();
                        encoder.release();
                        muxer.stop();
                        muxer.release();
                        muxerStarted = false;
                        extractor.release();
                        Log.e(Tag.DEBUG.toString(), "onError: ", e);
                    }

                    @Override
                    public void onOutputFormatChanged(@NonNull MediaCodec encoder, @NonNull MediaFormat format) {

                    }
                };
                MediaCodec encoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
                encoder.configure(outputFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
                encoder.setCallback(encoderCallbacks);
                encoder.start();
            } catch (Exception e) {
                Log.e(Tag.DEBUG.toString(), "onCreate: ", e);
            }
        });

然后我尝试在编码之前解码:

(我知道我无法使用此代码对大文件进行编码,否则

byteBuffersWithInfo
将会溢出。

所以我使用小音频文件来测试此代码)

startDecodingAndEncoding.setOnClickListener(
                v -> {
                    try {
                        outputFile.set(File.createTempFile("temp", null));
                        LinkedList<HashMap<String, Object>> byteBuffersWithInfo = new LinkedList<>();

                        MediaExtractor extractor = new MediaExtractor();
                        extractor.setDataSource(this, sourceUri.get(), null);
                        extractor.selectTrack(0);

                        MediaFormat outputFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 32000, 2);
                        outputFormat.setInteger(MediaFormat.KEY_BIT_RATE, 128000);

                        MediaMuxer muxer = new MediaMuxer(outputFile.get().getPath(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
                        int trackIndex = muxer.addTrack(outputFormat);

                        MediaCodec.Callback encoderCallbacks = new MediaCodec.Callback() {
                            boolean muxerStarted = false;
                            @Override
                            public void onInputBufferAvailable(@NonNull MediaCodec encoder, int index) {
                                Log.d(Tag.DEBUG.toString(), "onInputBufferAvailable: encoding...");
                                HashMap<String, Object> byteBufferWithInfo = byteBuffersWithInfo.poll();
                                if(byteBufferWithInfo != null) {
                                    ByteBuffer buffer = (ByteBuffer) byteBufferWithInfo.get("buffer");
                                    encoder.getInputBuffer(index).put(buffer);
                                    MediaCodec.BufferInfo info = (MediaCodec.BufferInfo) byteBufferWithInfo.get("info");
                                    encoder.queueInputBuffer(index, info.offset, info.size, info.presentationTimeUs, info.flags);
                                } else {
                                    Log.d(Tag.DEBUG.toString(), "onInputBufferAvailable: encoded.");
                                }
                            }

                            @Override
                            public void onOutputBufferAvailable(@NonNull MediaCodec encoder, int index, @NonNull MediaCodec.BufferInfo info) {
                                if(!muxerStarted){
                                    muxer.start();
                                    muxerStarted = true;
                                }
                                if (info.flags != MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
                                    muxer.writeSampleData(trackIndex, encoder.getOutputBuffer(index), info);
                                    Log.d(Tag.DEBUG.toString(), "onOutputBufferAvailable: writing to file...");
                                    encoder.releaseOutputBuffer(index, false);
                                } else {
                                    Log.d(Tag.DEBUG.toString(), "onOutputBufferAvailable: Writing completed.");
                                    encoder.stop();
                                    encoder.release();
                                    muxer.stop();
                                    muxer.release();
                                    muxerStarted = false;
                                    Log.d(Tag.DEBUG.toString(), "onOutputBufferAvailable: Resources released");
                                    Log.d(Tag.DEBUG.toString(),
                                            "Source file size: " + new File(sourceUri.get().getPath()).length() +
                                            "\nTarget file size: " + outputFile.get().length());
                                }
                            }

                            @Override
                            public void onError(@NonNull MediaCodec encoder, @NonNull MediaCodec.CodecException e) {
                                encoder.stop();
                                encoder.release();
                                muxer.stop();
                                muxer.release();
                                muxerStarted = false;
                                Log.e(Tag.DEBUG.toString(), "onError: ", e);
                            }

                            @Override
                            public void onOutputFormatChanged(@NonNull MediaCodec encoder, @NonNull MediaFormat format) {

                            }
                        };
                        MediaCodec encoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
                        encoder.configure(outputFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
                        encoder.setCallback(encoderCallbacks);

                        MediaCodec.Callback decoderCallbacks = new MediaCodec.Callback() {
                            int sampleSize;
                            long sampleTime;
                            boolean EOSFlag = false;
                            boolean encoderStarted = false;
                            @Override
                            public void onInputBufferAvailable(@NonNull MediaCodec decoder, int index) {
                                if(EOSFlag){
                                    decoder.queueInputBuffer(index, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                                    return;
                                }
                                ByteBuffer inputBuffer = decoder.getInputBuffer(index);
                                sampleSize = extractor.readSampleData(inputBuffer, 0);
                                if(sampleSize >= 0){
                                    sampleTime = extractor.getSampleTime();
                                    decoder.queueInputBuffer(index, 0, sampleSize, sampleTime, 0);
                                    Log.d(Tag.DEBUG.toString(), "onInputBufferAvailable: decoding...");
                                } else {
                                    EOSFlag = true;
                                    decoder.queueInputBuffer(index, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                                    extractor.release();
                                    Log.d(Tag.DEBUG.toString(), "onInputBufferAvailable: decoding completed by extractor.getSampleSize() < 0");
                                    return;
                                }
                                if(!extractor.advance()){
                                    EOSFlag = true;
                                    Log.d(Tag.DEBUG.toString(), "onInputBufferAvailable: decoding completed by extractor.advance() == false");
                                    extractor.release();
                                }
                            }

                            @Override
                            public void onOutputBufferAvailable(@NonNull MediaCodec decoder, int index, @NonNull MediaCodec.BufferInfo info) {
                                Log.d(Tag.DEBUG.toString(), "onOutputBufferAvailable: filling middle buffer...");
                                HashMap<String, Object> byteBufferWithInfo = new HashMap<>();
                                byteBufferWithInfo.put("buffer", decoder.getOutputBuffer(index));
                                byteBufferWithInfo.put("info", info);
                                byteBuffersWithInfo.offer(byteBufferWithInfo);
                                decoder.releaseOutputBuffer(index, false);
                                if(info.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM){
                                    Log.d(Tag.DEBUG.toString(), "onOutputBufferAvailable: middle buffer filled.");
                                    if(!encoderStarted){
                                        encoder.start();
                                        encoderStarted = true;
                                        decoder.stop();
                                    }
                                }
                            }

                            @Override
                            public void onError(@NonNull MediaCodec decoder, @NonNull MediaCodec.CodecException e) {
                                decoder.stop();
                                decoder.release();
                                extractor.release();
                                encoderStarted = false;
                                Log.e(Tag.DEBUG.toString(), "onError: ", e);
                            }

                            @Override
                            public void onOutputFormatChanged(@NonNull MediaCodec decoder, @NonNull MediaFormat format) {

                            }
                        };

                        MediaCodec decoder = MediaCodec.createDecoderByType(extractor.getTrackFormat(0).getString(MediaFormat.KEY_MIME));
                        decoder.configure(extractor.getTrackFormat(0), null, null, 0);
                        decoder.setCallback(decoderCallbacks);
                        decoder.start();
                    } catch (Exception e) {
                        Log.e(Tag.DEBUG.toString(), "onCreate: ", e);
                    }
                }
        );

但是当我尝试使用 Media3 ExoPlayer 播放两者中的任何一个时,我得到:

  1. 垃圾音频

  2. ExoPlayer 中出现此错误

    Audio sink error
                                                                                                          androidx.media3.exoplayer.audio.AudioSink$UnexpectedDiscontinuityException: Unexpected audio track timestamp discontinuity: expected 1000000480000, got 1000000689343
                                                                                                              at androidx.media3.exoplayer.audio.DefaultAudioSink.handleBuffer(DefaultAudioSink.java:956)
                                                                                                              at androidx.media3.exoplayer.audio.MediaCodecAudioRenderer.processOutputBuffer(MediaCodecAudioRenderer.java:739)
                                                                                                              at androidx.media3.exoplayer.mediacodec.MediaCodecRenderer.drainOutputBuffer(MediaCodecRenderer.java:1998)
                                                                                                              at androidx.media3.exoplayer.mediacodec.MediaCodecRenderer.render(MediaCodecRenderer.java:827)
                                                                                                              at androidx.media3.exoplayer.ExoPlayerImplInternal.doSomeWork(ExoPlayerImplInternal.java:1079)
                                                                                                              at androidx.media3.exoplayer.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:529)
                                                                                                              at android.os.Handler.dispatchMessage(Handler.java:102)
                                                                                                              at android.os.Looper.loopOnce(Looper.java:230)
                                                                                                              at android.os.Looper.loop(Looper.java:319)
                                                                                                              at android.os.HandlerThread.run(HandlerThread.java:67)
    
  3. 日志中有很多这样的警告:

    Codec2Client: query -- param skipped: index = 1342179345.
    

请告诉我我错过了什么。

这是我在社区的第一篇帖子,所以请原谅我的任何不当行为。

java audio encoding decoding android-mediacodec
1个回答
0
投票

首先,是的,您必须在将文件输入编码器之前对其进行解码。

其次,

MediaCodec
API 不处理采样率转换(例如从 48,000 Hz 到 32,000 Hz)。您需要为此使用第三方库或编写自己的代码。

关于您的代码。当您从解码器获得输出缓冲区时,将其保存在链接列表中。在下一行中你释放它。这是一个错误。

解码器管理可重用缓冲区的队列。一旦输出缓冲区被释放到编解码器,就不得使用它。当您从列表中获取缓冲区时,它可能已被其他数据覆盖。解决此问题的一种方法是复制缓冲区。

© www.soinside.com 2019 - 2024. All rights reserved.