Android aac文件解码为pcm

我们知道mp4文件是由音频轨和视频轨组成的,我们现在提取出aac文件,然后转为pcm。

其实mp4的音频轨和aac的音频轨转化为pcm操作是一样的。都是找到音频轨的索引index,然后取数据转码。

这里要设计到几个api

 @NonNull
    public static MediaCodec createDecoderByType(@NonNull String type)
            throws IOException {
        return new MediaCodec(type, true /* nameIsType */, false /* encoder */);
    }

这个函数我们会根据文件的类型,创建不同的解码器

文件类型又是如何获取的呢?

我们会通过 

MediaExtractor中的
 for (int i = 0; i < mExtractor.getTrackCount(); ++i) {
                MediaFormat format = mExtractor.getTrackFormat(i);
                String mime = format.getString(MediaFormat.KEY_MIME);
                Log.d(TAG, "mime=" + mime);
                if (mime.startsWith("audio/")) {
                    audioTrack = i;
                    hasAudio = true;
                    mFormat = format;
                    break;
                }
            }

我们通过一个for循环,从多媒体文件当中通过getTrackFormat选择我们需要的音频轨

 /**
     * Subsequent calls to {@link #readSampleData}, {@link #getSampleTrackIndex} and
     * {@link #getSampleTime} only retrieve information for the subset of tracks
     * selected.
     * Selecting the same track multiple times has no effect, the track is
     * only selected once.
     */
    public native void selectTrack(int index);
/**
     * Retrieve the current encoded sample and store it in the byte buffer
     * starting at the given offset.
     * <p>
     * <b>Note:</b>As of API 21, on success the position and limit of
     * {@code byteBuf} is updated to point to the data just read.
     * @param byteBuf the destination byte buffer
     * @return the sample size (or -1 if no more samples are available).
     */
    public native int readSampleData(@NonNull ByteBuffer byteBuf, int offset);

我们通过selectTrack设置我们要exector的轨道,通过readSampleData读取我们压缩的数据。

解码过程我们要用到几个api

 /**
     * Returns the index of an input buffer to be filled with valid data
     * or -1 if no such buffer is currently available.
     * This method will return immediately if timeoutUs == 0, wait indefinitely
     * for the availability of an input buffer if timeoutUs &lt; 0 or wait up
     * to "timeoutUs" microseconds if timeoutUs &gt; 0.
     * @param timeoutUs The timeout in microseconds, a negative timeout indicates "infinite".
     * @throws IllegalStateException if not in the Executing state,
     *         or codec is configured in asynchronous mode.
     * @throws MediaCodec.CodecException upon codec error.
     */
    public final int dequeueInputBuffer(long timeoutUs)

可以理解为native层有一个buffer数组,这个函数从buffer数组中返回当前可用的数组索引。

 /**
     * Returns a {@link java.nio.Buffer#clear cleared}, writable ByteBuffer
     * object for a dequeued input buffer index to contain the input data.
     *
     * After calling this method any ByteBuffer or Image object
     * previously returned for the same input index MUST no longer
     * be used.
     *
     * @param index The index of a client-owned input buffer previously
     *              returned from a call to {@link #dequeueInputBuffer},
     *              or received via an onInputBufferAvailable callback.
     *
     * @return the input buffer, or null if the index is not a dequeued
     * input buffer, or if the codec is configured for surface input.
     *
     * @throws IllegalStateException if not in the Executing state.
     * @throws MediaCodec.CodecException upon codec error.
     */
    @Nullable
    public ByteBuffer getInputBuffer(int index)

返回索引指示的buffer

如何将数据写入buffer呢

我们通过MediaExtractor的api将数据写入buffer中

    public native int readSampleData(@NonNull ByteBuffer byteBuf, int offset);

写入数据后,我们需要将buffer入队。通过下面的函数 

    public final void queueInputBuffer(
            int index,
            int offset, int size, long presentationTimeUs, int flags)

将当前index对应的buffer入队,这样mediaCodec就可以开始解码了。

上面写了如何给解码器输入数据,那么如何从解码器读取数据呢?

    public ByteBuffer getOutputBuffer(int index) 

这个index和我们上面使用的index是一个,通过这里我们可以猜测到MediaCodec的底层用两个buffer数组,一个in一个out,数组大小是一样的。输入和输出是一一对应的。

我们buffer数据使用完后,要释放空间

    public final void releaseOutputBuffer(int index, boolean render) 

下面是完整的代码

package com.yuanxuzhen.androidmedia.decode;

import android.media.MediaCodec;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.os.Build;
import android.util.Log;

import com.yuanxuzhen.androidmedia.demux.AudioMediaInfo;
import com.yuanxuzhen.androidmedia.demux.MediaUtil;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;

public class AacDecoder {
    public static final int ERROR_INPUT_INVALID = 100;
    public static final int ERROR_OUTPUT_FAILED = 200;
    public static final int ERROR_OPEN_CODEC = 300;
    public static final int OK = 0;
    private static final int TIMEOUT_USEC = 0;
    public String TAG = "AAC_DECODER";
    private MediaExtractor mExtractor;
    private MediaFormat mFormat;
    private FileOutputStream mFos;
    private MediaCodec mDecoder;
    private ByteBuffer[] mInputBuffers;
    private ByteBuffer[] mOutputBuffers;
    private boolean mDecodeEnd = false;

    public int decode(String audioPath, String pcmPath){
        int ret;
        AudioMediaInfo audioMediaInfo = MediaUtil.getAudioMeidaInfo(audioPath);
        Log.e(TAG, "decode " + audioMediaInfo);
        if (OK != (ret = openInput(audioPath))) {
            return ret;
        }
        if (OK != (ret = openOutput(pcmPath))) {
            return ret;
        }
        if (OK != (ret = openCodec(mFormat))) {
            return ret;
        }
        mDecodeEnd = false;
        while (!mDecodeEnd) {
            if (OK != (ret = decode(mDecoder, mExtractor))) {
                Log.d(TAG, "decode failed, ret=" + ret);
                break;
            }
        }
        close();
        Log.d(TAG, "decode end" + ret);

        return ret;
    }

    private int decode(MediaCodec codec, MediaExtractor extractor) {
        Log.d(TAG, "decode");
        int inputIndex = codec.dequeueInputBuffer(TIMEOUT_USEC);
        if (inputIndex >= 0) {
            ByteBuffer inputBuffer;
            if (Build.VERSION.SDK_INT >= 21) {
                inputBuffer = codec.getInputBuffer(inputIndex);
            } else {
                inputBuffer = mInputBuffers[inputIndex];
            }
            inputBuffer.clear();
            int sampleSize = extractor.readSampleData(inputBuffer, 0);
            if (sampleSize < 0) {//read end
                codec.queueInputBuffer(inputIndex, 0, 0, 0L,
                        MediaCodec.BUFFER_FLAG_END_OF_STREAM);
            } else {
                codec.queueInputBuffer(inputIndex, 0, sampleSize, extractor.getSampleTime(), 0);
                extractor.advance();
            }
        }

        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
        int outputIndex = codec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);
        if (outputIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {//TIMEOUT
            Log.d(TAG, "INFO_TRY_AGAIN_LATER");//TODO how to declare this info
            return OK;
        } else if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
            Log.d(TAG, "output format changed");
            return OK;
        } else if (outputIndex < 0) {
            Log.d(TAG, "outputIndex=" + outputIndex);
            return OK;
        } else {
            ByteBuffer outputBuffer;
            if (Build.VERSION.SDK_INT >= 21) {
                outputBuffer = codec.getOutputBuffer(outputIndex);
            } else {
                outputBuffer = mOutputBuffers[outputIndex];
            }
            byte[] buffer = new byte[bufferInfo.size];
            outputBuffer.get(buffer);
            try {
                Log.d(TAG, "output write, size="+ bufferInfo.size);
                mFos.write(buffer);
                mFos.flush();
            } catch (IOException e) {
                e.printStackTrace();
                return ERROR_OUTPUT_FAILED;
            }
            codec.releaseOutputBuffer(outputIndex, false);
            if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                mDecodeEnd = true;
            }
        }
        return OK;
    }

    private int checkPath(String path) {
        if (path == null || path.isEmpty()) {
            Log.d(TAG, "invalid path, path is empty");
            return ERROR_INPUT_INVALID;
        }
        File file = new File(path);
        if (!file.isFile()) {
            Log.d(TAG, "path is not a file, path:" + path);
            return ERROR_INPUT_INVALID;
        } else if (!file.exists()) {
            Log.d(TAG, "file not exists, path:" + path);
            return ERROR_INPUT_INVALID;
        } else {
            Log.d(TAG, "path is a file, path:" + path);
        }
        return OK;
    }



    private int openInput(String audioPath) {
        Log.d(TAG, "openInput audioPath:" + audioPath);
        int ret;
        if (OK != (ret = checkPath(audioPath))) {
            return ret;
        }
        mExtractor = new MediaExtractor();
        int audioTrack = -1;
        boolean hasAudio = false;
        try {
            mExtractor.setDataSource(audioPath);
            for (int i = 0; i < mExtractor.getTrackCount(); ++i) {
                MediaFormat format = mExtractor.getTrackFormat(i);
                String mime = format.getString(MediaFormat.KEY_MIME);
                Log.d(TAG, "mime=" + mime);
                if (mime.startsWith("audio/")) {
                    audioTrack = i;
                    hasAudio = true;
                    mFormat = format;
                    break;
                }
            }
            if (!hasAudio) {
                Log.d(TAG, "input contain no audio");
                return ERROR_INPUT_INVALID;
            }
            mExtractor.selectTrack(audioTrack);
        } catch (IOException e) {
            return ERROR_INPUT_INVALID;
        }
        return OK;
    }

    private int openOutput(String outputPath) {
        Log.d(TAG, "openOutput outputPath:" + outputPath);
        try {
            mFos = new FileOutputStream(outputPath);
        } catch (IOException e) {
            return ERROR_OUTPUT_FAILED;
        }
        return OK;
    }

    private int openCodec(MediaFormat format) {
        Log.d(TAG, "openCodec, format mime:" + format.getString(MediaFormat.KEY_MIME));
        try {
            mDecoder = MediaCodec.createDecoderByType(format.getString(MediaFormat.KEY_MIME));
        } catch (IOException e) {
            e.printStackTrace();
            return ERROR_OPEN_CODEC;
        }
        mDecoder.configure(format, null, null, 0);
        mDecoder.start();
        if (Build.VERSION.SDK_INT < 21) {
            mInputBuffers = mDecoder.getInputBuffers();
            mOutputBuffers = mDecoder.getOutputBuffers();
        }
        return OK;
    }

    private void close() {
        mExtractor.release();
        mDecoder.stop();
        mDecoder.release();
        try {
            mFos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

gitee地址

https://gitee.com/creat151/android-media.git

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值