android 使用MediaCodec(根据设备状况硬编解码)来转码音频(MP3 to aac),并同时裁剪音频

第一次写这样的博文,写得不好,望大家及时指出博文中的错误,以便于相互学习。

本来主要介绍如何使用mediacodec来转码音频,同时实现音频的裁剪。对于mediacodec相信大家都不陌生,没做过也听过。

转码音频需要的知识点

1、音频基础知识,什么是pcm,什么是音频格式(MP3、aac等),这里给大家推荐一篇博文,http://blog.csdn.net/kevindgk/article/details/52924779

2、mediacodec的基础知识和api调用,mediacodec是典型的CS模式,也就是客户端-服务器模式,建议大家多看看官网api,

这里给大家推荐一篇中文版的api,MediaCodec中文api

转码流程

解码线程:FILE—解码—PCM队列

编码线程:PCM队列—编码—FILE

系统版本和相关Api类

sdk版本>=16;MediaCodec、MediaExtractor

解码线程

主要流程是通过MediaExtractor读取一帧数据,然后进行解码并放入PCM队列,流程分为prepare—decode—release;
相关代码分别如下:
主要线程里面的流程:


  @Override
        public void run() {
            TransAacHandlerPure.logMsg("decodec run");
            if (listener != null) {
                listener.onStart();
            }
            boolean isPrepare = false;
            try {
                prepare();//初始化
                isPrepare = true;
            } catch (IOException e) {
                e.printStackTrace();
            }
            TransAacHandlerPure.logMsg("decodec isPrepare  " + isPrepare);
            if (isPrepare) {
                decode();//解码
            }
            release();//释放资源
            if (!isPrepare && listener != null) {
                listener.onFail();
            }
            isFinish = true;
        }

prepare里面的代码如下:
   private void prepare() throws IOException {
            extractor = new MediaExtractor();
            extractor.setDataSource(srcFile);
            int numTracks = extractor.getTrackCount();
            for (int i = 0; i < numTracks; i++) {
                MediaFormat format = extractor.getTrackFormat(i);
                String mine = format.getString(MediaFormat.KEY_MIME);
                if (!TextUtils.isEmpty(mine) && mine.startsWith("audio")) {
                    extractor.selectTrack(i);
                    try {
                        duration = format.getInteger(MediaFormat.KEY_DURATION) / 1000;
                    } catch (Exception e) {
                        e.printStackTrace();
                        MediaPlayer mediaPlayer = new MediaPlayer();
                        mediaPlayer.setDataSource(srcFile);
                        mediaPlayer.prepare();
                        duration = mediaPlayer.getDuration();
                        mediaPlayer.release();
                    }
                    codec = MediaCodec.createDecoderByType(mine);
                    codec.configure(format, null, null, 0);
                    codec.start();
                    TransAacHandlerPure.logMsg("New decode codec start:" + format.toString());
                    break;
                }
            }
//            createFile(outFile + ".pcm", true);//测试  输出pcm格式
//            mOutput = new DataOutputStream(new FileOutputStream(outFile + ".pcm"));
        }
decode方法如下:

        private void decode() {
            ByteBuffer[] inputBuffers = codec.getInputBuffers();
            ByteBuffer[] outputBuffers = codec.getOutputBuffers();
            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
            TransAacHandlerPure.logMsg("loopDecode   start");
            if (rangeStart > 0) {//如果有裁剪,seek到裁剪的地方
                extractor.seekTo(rangeStart * 1000, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
            }
            boolean isEOS = false;
            while (true) {
                long timestamp = 0;
                if (!isEOS) {
                    int inIndex = codec.dequeueInputBuffer(TIME_OUT);
                    if (inIndex >= 0) {
                        ByteBuffer buffer = inputBuffers[inIndex];
                        int sampleSize = extractor.readSampleData(buffer, 0);
                        long timestampTemp = extractor.getSampleTime();
                        timestamp = timestampTemp / 1000;
                        TransAacHandlerPure.logMsg("loopDecode  readSampleData end sampleSize  " + sampleSize + "    buffer.capacity()=" + buffer.capacity());
                        TransAacHandlerPure.logMsg("loopDecode  readSampleData end timestamp" + timestamp);
                        if (rangeEnd > 0 && timestamp > rangeEnd) {
                            sampleSize = -1;
                        }
                        if (sampleSize <= 0) {
                            codec.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                            isEOS = true;
                        } else {
                            codec.queueInputBuffer(inIndex, 0, sampleSize, timestampTemp, 0);
                            extractor.advance();
                        }
                    }
                }
                int outIndex = codec.dequeueOutputBuffer(info, TIME_OUT);
//                TransAacHandlerPure.logMsg(" switch (outIndex)");
                switch (outIndex) {
                    case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                        outputBuffers = codec.getOutputBuffers();
                        TransAacHandlerPure.logMsg("dequeueOutputBuffer INFO_OUTPUT_BUFFERS_CHANGED!");
                        break;
                    case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                        MediaFormat mf = codec.getOutputFormat();
                        //开始编码线程
                        EncodeTask encodeTask = new EncodeTask(outFile, this, listener);
                        int sampleRate = mf.getInteger(MediaFormat.KEY_SAMPLE_RATE);
                        int pcmEncoding = mf.getInteger(MediaFormat.KEY_PCM_ENCODING);
                        int channelCount = mf.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
                        encodeTask.setAudioParams(sampleRate, pcmEncoding, channelCount);
                        new Thread(encodeTask).start();
                        TransAacHandlerPure.logMsg("New format " + mf.toString());
                        break;
                    case MediaCodec.INFO_TRY_AGAIN_LATER:
                        TransAacHandlerPure.logMsg("dequeueOutputBuffer timed out!");
                        break;
                    default:
                        if (last == 0) {
                            last = System.currentTimeMillis();
                        }
                        long now = System.currentTimeMillis();
                        TransAacHandlerPure.logMsg("解码时间:" + (now - last) + " info.size  " + info.size);
                        last = now;
                        ByteBuffer buffer = outputBuffers[outIndex];
                        byte[] outData = new byte[info.size];
                        buffer.get(outData, 0, info.size);
                        codec.releaseOutputBuffer(outIndex, true);
                        try {
                            mOutput.write(outData);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        pushAvFrame(outData);
                        if (listener != null) {
                            listener.onProgress(rangeEnd > 0 ? (int) rangeEnd : duration, rangeStart > 0 ? (int) (timestamp - rangeStart) : (int) timestamp);
                        }
                        break;
                }
                // All decoded frames have been rendered, we can stop playing now
                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                    TransAacHandlerPure.logMsg("OutputBuffer BUFFER_FLAG_END_OF_STREAM");
                    break;
                }
            }
        }
release相关代码

        private void release() {
            if (extractor != null) {
                extractor.release();
                extractor = null;
            }
            if (codec != null) {
                codec.stop();
                codec.release();
                codec = null;
            }
        }

编码线程

主要流程跟解码线程类似,需要说明的是编码线程开始是在解码返回 MediaCodec.INFO_OUTPUT_FORMAT_CHANGED这个状态的时候,因为在这个时候可以拿到编码需要的一些必要参数,如采样率等等,虽然也可以在从MediaExtractor里面拿,但是这样可能有些机型拿不到一些数据,所以我认为在这里拿是比较合适的。闲话不多说,直接看代码:
run方法:
 @Override
        public void run() {
            boolean isPrepare = false;
            try {
                prepare();
                isPrepare = true;
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (isPrepare && obtain != null) {
                encode();
            }
            release();
            if (listener != null) {
                if (isPrepare) {
                    listener.onSuccess();
                } else {
                    listener.onFail();
                }

            }

        }
prepare方法,关于比特率的设置,大家多参考之前关于音频基础知识的那篇博文,这个直接影响到文件大小和音质
 private void prepare() throws IOException {
            String mime = MediaFormat.MIMETYPE_AUDIO_AAC;
            encoder = MediaCodec.createEncoderByType(mime);
            MediaFormat format = MediaFormat.createAudioFormat(mime, sampleRate, channelCount);
            format.setInteger(MediaFormat.KEY_BIT_RATE, 96000);
            format.setInteger(MediaFormat.KEY_PCM_ENCODING, pcmEncoding);
            format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 20 * 1024);
            format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
            logMsg(" New  " + format.toString());
            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
            encoder.start();
            createFile(outFile, true);
            mOutput = new DataOutputStream(new FileOutputStream(outFile));
        }

encode方法,要注意aac写入的时候需要加入头部分,关于aac格式介绍,大家参考这篇博文- aac格式介绍
        private void encode() {
            boolean isFinish = false;
            while (true) {
                if (!isFinish) {
                    byte[] rawData = obtain.getRawFrame();
                    if (rawData == null) {
                        if (obtain.isFinish()) {
                            isFinish = true;
                            int inIndex = encoder.dequeueInputBuffer(TIME_OUT);
                            encoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                        } else {
                            try {
                                Thread.sleep(10);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        continue;
                    }
                    ByteBuffer[] inputBuffers = encoder.getInputBuffers();

                    int inIndex = encoder.dequeueInputBuffer(TIME_OUT);
                    if (inIndex >= 0) {
                        ByteBuffer inputBuffer = inputBuffers[inIndex];
                        inputBuffer.clear();
                        inputBuffer.put(rawData);
                        encoder.queueInputBuffer(inIndex, 0, rawData.length, System.nanoTime(), 0);
                    }
                }
                ByteBuffer[] outputBuffers = encoder.getOutputBuffers();
                MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
                int outIndex = encoder.dequeueOutputBuffer(info, TIME_OUT);
                if (outIndex >= 0) {
                    if (last == 0) {
                        last = System.currentTimeMillis();
                    }
                    long now = System.currentTimeMillis();
                    TransAacHandlerPure.logMsg("编码码时间:" + (now - last) + " info.size  " + info.size);
                    last = now;
                    while (outIndex >= 0) {
                        ByteBuffer outputBuffer = outputBuffers[outIndex];
                        int len = info.size + 7;
                        byte[] outData = new byte[len];
                        addADTStoPacket(outData, len);
                        outputBuffer.get(outData, 7, info.size);
                        encoder.releaseOutputBuffer(outIndex, false);
                        try {
                            mOutput.write(outData);
                        } catch (Exception e) {
                            e.printStackTrace();
                        } catch (Error e) {
                            e.printStackTrace();
                        }
                        outIndex = encoder.dequeueOutputBuffer(info, TIME_OUT);
                    }
                }
                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                    VLog.d("encode OutputBuffer BUFFER_FLAG_END_OF_STREAM");
                    break;
                }
            }
        }


加入aac头方法
 /**
         * 给编码出的aac裸流添加adts头字段
         *
         * @param packet    要空出前7个字节,否则会搞乱数据
         * @param packetLen
         */
        private void addADTStoPacket(byte[] packet, int packetLen) {
            int profile = 2;  //AAC LC
            int freqIdx = 4;  //44.1KHz
            int chanCfg = 2;  //CPE
            packet[0] = (byte) 0xFF;
            packet[1] = (byte) 0xF9;
            packet[2] = (byte) (((profile - 1) << 6) + (freqIdx << 2) + (chanCfg >> 2));
            packet[3] = (byte) (((chanCfg & 3) << 6) + (packetLen >> 11));
            packet[4] = (byte) ((packetLen & 0x7FF) >> 3);
            packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F);
            packet[6] = (byte) 0xFC;
        }

release方法
        private void release() {
            if (encoder != null) {
                encoder.stop();
                encoder.release();
                encoder = null;
            }
            if (mOutput != null) {
                try {
                    mOutput.flush();
                    mOutput.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                mOutput = null;
            }
        }

几个需要注意的地方

1、编码线程初始化的地方,获取初始化所需的必要参数
2、编解码线程之间的同步,由于解码线程一般比编码线程快,所以pcm的队列数据同步显得比较重要,当然了这个消费者模式的基础,相信大家很容易理解
3、aac文件的保存,需要添加头

纯净版MP3ToAAc(可以同时剪辑)所有代码

import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.media.MediaPlayer;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.text.TextUtils;
import android.util.Log;

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * 音频转换成为aac编码
 *
 * @author jake
 * @since 2017/8/3 下午4:28
 */
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
public class TransAacHandlerPure {
    private String srcFile;
    private String outFile;
    private long rangeStart = -1;
    private long rangeEnd = -1;
    private OnProgressListener listener;


    public TransAacHandlerPure(String srcFile, String outFile) {
        this(srcFile, outFile, null);
    }

    public TransAacHandlerPure(String srcFile, String outFile, OnProgressListener listener) {
        this(srcFile, outFile, -1, -1, listener);
    }

    public TransAacHandlerPure(String srcFile, String outFile, long rangeStart, long rangeEnd, OnProgressListener listener) {
        this.srcFile = srcFile;
        this.outFile = outFile;
        this.rangeStart = rangeStart;
        this.rangeEnd = rangeEnd;
        this.listener = listener;
    }

    public void start() {
        DecodeTask task = new DecodeTask(srcFile, outFile, listener);
        task.setRangeTime(rangeStart, rangeEnd);
        new Thread(task).start();
    }

    public void setRangeTime(long rangeStart, long rangeEnd) {
        this.rangeStart = rangeStart;
        this.rangeEnd = rangeEnd;
    }


    public void setListener(OnProgressListener listener) {
        this.listener = listener;
    }

    private static class DecodeTask implements Runnable, IDataObtain {
        private static final long TIME_OUT = 5000;
        private Queue<byte[]> mRawQueue;
        private MediaExtractor extractor;
        private boolean isFinish = false;
        private String srcFile;
        private MediaCodec codec;
        private String outFile;
        private OnProgressListener listener;
        private long rangeStart;
        private long rangeEnd;
        private int duration = 0;
        private OutputStream mOutput;

        public void setRangeTime(long rangeStart, long rangeEnd) {
            this.rangeStart = rangeStart;
            this.rangeEnd = rangeEnd;
        }

        public DecodeTask(String srcFile, String outFile, OnProgressListener listener) {
            this.srcFile = srcFile;
            this.outFile = outFile;
            this.listener = listener;
            mRawQueue = new LinkedBlockingQueue<>();
        }

        private void pushAvFrame(byte[] frame) {
            if (frame != null) {
                int len = mRawQueue.size();
                while (len > 10) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    len = mRawQueue.size();
                }
                synchronized (mRawQueue) {
                    mRawQueue.offer(frame);
                }
            }
        }


        @Override
        public void run() {
            TransAacHandlerPure.logMsg("decodec run");
            if (listener != null) {
                listener.onStart();
            }
            boolean isPrepare = false;
            try {
                prepare();
                isPrepare = true;
            } catch (IOException e) {
                e.printStackTrace();
            }
            TransAacHandlerPure.logMsg("decodec isPrepare  " + isPrepare);
            if (isPrepare) {
                decode();
            }
            release();
            if (!isPrepare && listener != null) {
                listener.onFail();
            }
            isFinish = true;
        }

        private void release() {
            if (extractor != null) {
                extractor.release();
                extractor = null;
            }
            if (codec != null) {
                codec.stop();
                codec.release();
                codec = null;
            }
        }


        private void prepare() throws IOException {
            extractor = new MediaExtractor();
            extractor.setDataSource(srcFile);
            int numTracks = extractor.getTrackCount();
            for (int i = 0; i < numTracks; i++) {
                MediaFormat format = extractor.getTrackFormat(i);
                String mine = format.getString(MediaFormat.KEY_MIME);
                if (!TextUtils.isEmpty(mine) && mine.startsWith("audio")) {
                    extractor.selectTrack(i);
                    try {
                        duration = format.getInteger(MediaFormat.KEY_DURATION) / 1000;
                    } catch (Exception e) {
                        e.printStackTrace();
                        MediaPlayer mediaPlayer = new MediaPlayer();
                        mediaPlayer.setDataSource(srcFile);
                        mediaPlayer.prepare();
                        duration = mediaPlayer.getDuration();
                        mediaPlayer.release();
                    }
                    codec = MediaCodec.createDecoderByType(mine);
                    codec.configure(format, null, null, 0);
                    codec.start();
                    TransAacHandlerPure.logMsg("New decode codec start:" + format.toString());
                    break;
                }
            }
            createFile(outFile + ".pcm", true);//测试  输出pcm格式
            mOutput = new DataOutputStream(new FileOutputStream(outFile + ".pcm"));
        }

        long last;

        private void decode() {
            ByteBuffer[] inputBuffers = codec.getInputBuffers();
            ByteBuffer[] outputBuffers = codec.getOutputBuffers();
            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
            TransAacHandlerPure.logMsg("loopDecode   start");
            if (rangeStart > 0) {//如果有裁剪,seek到裁剪的地方
                extractor.seekTo(rangeStart * 1000, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
            }
            boolean isEOS = false;
            while (true) {
                long timestamp = 0;
                if (!isEOS) {
                    int inIndex = codec.dequeueInputBuffer(TIME_OUT);
                    if (inIndex >= 0) {
                        ByteBuffer buffer = inputBuffers[inIndex];
                        int sampleSize = extractor.readSampleData(buffer, 0);
                        long timestampTemp = extractor.getSampleTime();
                        timestamp = timestampTemp / 1000;
                        TransAacHandlerPure.logMsg("loopDecode  readSampleData end sampleSize  " + sampleSize + "    buffer.capacity()=" + buffer.capacity());
                        TransAacHandlerPure.logMsg("loopDecode  readSampleData end timestamp" + timestamp);
                        if (rangeEnd > 0 && timestamp > rangeEnd) {
                            sampleSize = -1;
                        }
                        if (sampleSize <= 0) {
                            codec.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                            isEOS = true;
                        } else {
                            codec.queueInputBuffer(inIndex, 0, sampleSize, timestampTemp, 0);
                            extractor.advance();
                        }
                    }
                }
                int outIndex = codec.dequeueOutputBuffer(info, TIME_OUT);
//                TransAacHandlerPure.logMsg(" switch (outIndex)");
                switch (outIndex) {
                    case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                        outputBuffers = codec.getOutputBuffers();
                        TransAacHandlerPure.logMsg("dequeueOutputBuffer INFO_OUTPUT_BUFFERS_CHANGED!");
                        break;
                    case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                        MediaFormat mf = codec.getOutputFormat();
                        //开始编码线程
                        EncodeTask encodeTask = new EncodeTask(outFile, this, listener);
                        int sampleRate = mf.getInteger(MediaFormat.KEY_SAMPLE_RATE);
                        int pcmEncoding = mf.getInteger(MediaFormat.KEY_PCM_ENCODING);
                        int channelCount = mf.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
                        encodeTask.setAudioParams(sampleRate, pcmEncoding, channelCount);
                        new Thread(encodeTask).start();
                        TransAacHandlerPure.logMsg("New format " + mf.toString());
                        break;
                    case MediaCodec.INFO_TRY_AGAIN_LATER:
                        TransAacHandlerPure.logMsg("dequeueOutputBuffer timed out!");
                        break;
                    default:
                        if (last == 0) {
                            last = System.currentTimeMillis();
                        }
                        long now = System.currentTimeMillis();
                        TransAacHandlerPure.logMsg("解码时间:" + (now - last) + " info.size  " + info.size);
                        last = now;
                        ByteBuffer buffer = outputBuffers[outIndex];
                        byte[] outData = new byte[info.size];
                        buffer.get(outData, 0, info.size);
                        codec.releaseOutputBuffer(outIndex, true);
                        try {
                            mOutput.write(outData);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        pushAvFrame(outData);
                        if (listener != null) {
                            listener.onProgress(rangeEnd > 0 ? (int) rangeEnd : duration, rangeStart > 0 ? (int) (timestamp - rangeStart) : (int) timestamp);
                        }
                        break;
                }
                // All decoded frames have been rendered, we can stop playing now
                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                    TransAacHandlerPure.logMsg("OutputBuffer BUFFER_FLAG_END_OF_STREAM");
                    break;
                }
            }
        }


        @Override
        public byte[] getRawFrame() {
            int len = mRawQueue.size();
            if (len > 0) {
                synchronized (mRawQueue) {
                    return mRawQueue.poll();
                }
            }
            return null;
        }

        @Override
        public boolean isFinish() {
            return isFinish;
        }
    }

    private static void logMsg(String msg) {
        Log.d(TransAacHandlerPure.class.getSimpleName(), msg);
    }

    private static class EncodeTask implements Runnable {
        private static final long TIME_OUT = 5000;
        private IDataObtain obtain;
        private String outFile;
        private MediaCodec encoder;
        private OutputStream mOutput;
        private OnProgressListener listener;
        private long last;
        private int sampleRate;
        private int pcmEncoding;
        private int channelCount;

        public EncodeTask(String outFile, IDataObtain obtain, OnProgressListener listener) {
            this.obtain = obtain;
            this.outFile = outFile;
            this.listener = listener;
        }

        public void setAudioParams(int sampleRate, int pcmEncoding, int channelCount) {
            this.sampleRate = sampleRate;
            this.pcmEncoding = pcmEncoding;
            this.channelCount = channelCount;
        }

        @Override
        public void run() {
            boolean isPrepare = false;
            try {
                prepare();
                isPrepare = true;
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (isPrepare && obtain != null) {
                encode();
            }
            release();
            if (listener != null) {
                if (isPrepare) {
                    listener.onSuccess();
                } else {
                    listener.onFail();
                }

            }

        }

        private void release() {
            if (encoder != null) {
                encoder.stop();
                encoder.release();
                encoder = null;
            }
            if (mOutput != null) {
                try {
                    mOutput.flush();
                    mOutput.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                mOutput = null;
            }
        }

        private void encode() {
            boolean isFinish = false;
            while (true) {
                if (!isFinish) {
                    byte[] rawData = obtain.getRawFrame();
                    if (rawData == null) {
                        if (obtain.isFinish()) {
                            isFinish = true;
                            int inIndex = encoder.dequeueInputBuffer(TIME_OUT);
                            encoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                        } else {
                            try {
                                Thread.sleep(10);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        continue;
                    }
                    ByteBuffer[] inputBuffers = encoder.getInputBuffers();

                    int inIndex = encoder.dequeueInputBuffer(TIME_OUT);
                    if (inIndex >= 0) {
                        ByteBuffer inputBuffer = inputBuffers[inIndex];
                        inputBuffer.clear();
                        inputBuffer.put(rawData);
                        encoder.queueInputBuffer(inIndex, 0, rawData.length, System.nanoTime(), 0);
                    }
                }
                ByteBuffer[] outputBuffers = encoder.getOutputBuffers();
                MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
                int outIndex = encoder.dequeueOutputBuffer(info, TIME_OUT);
                if (outIndex >= 0) {
                    if (last == 0) {
                        last = System.currentTimeMillis();
                    }
                    long now = System.currentTimeMillis();
                    TransAacHandlerPure.logMsg("编码码时间:" + (now - last) + " info.size  " + info.size);
                    last = now;
                    while (outIndex >= 0) {
                        ByteBuffer outputBuffer = outputBuffers[outIndex];
                        int len = info.size + 7;
                        byte[] outData = new byte[len];
                        addADTStoPacket(outData, len);
                        outputBuffer.get(outData, 7, info.size);
                        encoder.releaseOutputBuffer(outIndex, false);
                        try {
                            mOutput.write(outData);
                        } catch (Exception e) {
                            e.printStackTrace();
                        } catch (Error e) {
                            e.printStackTrace();
                        }
                        outIndex = encoder.dequeueOutputBuffer(info, TIME_OUT);
                    }
                }
                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                    VLog.d("encode OutputBuffer BUFFER_FLAG_END_OF_STREAM");
                    break;
                }
            }
        }

        /**
         * 给编码出的aac裸流添加adts头字段
         *
         * @param packet    要空出前7个字节,否则会搞乱数据
         * @param packetLen
         */
        private void addADTStoPacket(byte[] packet, int packetLen) {
            int profile = 2;  //AAC LC
            int freqIdx = 4;  //44.1KHz
            int chanCfg = 2;  //CPE
            packet[0] = (byte) 0xFF;
            packet[1] = (byte) 0xF9;
            packet[2] = (byte) (((profile - 1) << 6) + (freqIdx << 2) + (chanCfg >> 2));
            packet[3] = (byte) (((chanCfg & 3) << 6) + (packetLen >> 11));
            packet[4] = (byte) ((packetLen & 0x7FF) >> 3);
            packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F);
            packet[6] = (byte) 0xFC;
        }

        private void prepare() throws IOException {
            String mime = MediaFormat.MIMETYPE_AUDIO_AAC;
            encoder = MediaCodec.createEncoderByType(mime);
            MediaFormat format = MediaFormat.createAudioFormat(mime, sampleRate, channelCount);
            format.setInteger(MediaFormat.KEY_BIT_RATE, 96000);
            format.setInteger(MediaFormat.KEY_PCM_ENCODING, pcmEncoding);
            format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 20 * 1024);
            format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
            logMsg(" New  " + format.toString());
            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
            encoder.start();
            createFile(outFile, true);
            mOutput = new DataOutputStream(new FileOutputStream(outFile));
        }

    }

    private static boolean createFile(String filePath, boolean recreate) {
        if (TextUtils.isEmpty(filePath)) {
            return false;
        }
        try {
            File file = new File(filePath);
            if (file.exists()) {
                if (recreate) {
                    file.delete();
                    file.createNewFile();
                }
            } else {
                // 如果路径不存在,先创建路径
                File parentFile = file.getParentFile();
                if (!parentFile.exists()) {
                    parentFile.mkdirs();
                }
                file.createNewFile();
            }
        } catch (Exception e) {
            return false;
        }
        return true;
    }

    public interface IDataObtain {
        byte[] getRawFrame();

        boolean isFinish();
    }

    public static interface OnProgressListener {
        void onStart();

        void onProgress(int max, int progress);

        void onSuccess();

        void onFail();
    }
}


关于音频裁剪比较简单,如果相同格式不需要转码,直接用MediaExtractor读取相关片段后直接写入即可,这样效率比较高。关于这个转码算法的效率还是比较低,主要原因是编码比较慢,虽然多线程解决了部分问题,但是效率还是比较慢,大家有兴趣可以提出修改意见,咱们一起优化一下这个算法!
由于准备匆忙,很多知识点都没有跟大家说清楚,望大家见谅!希望代码写得不是很乱,大家能看得懂,有不懂的地方,欢迎跟我沟通!




  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
MediaCodecAndroid中的一个多媒体编解码器,可以用于音频和视频编解码。下面是一个使用MediaCodec实现音频编解码的简单示例。 1. 初始化MediaCodec ``` MediaFormat format = MediaFormat.createAudioFormat("audio/mp4a-latm", sampleRate, channelCount); format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate); format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC); format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, bufferSize); MediaCodec codec = MediaCodec.createEncoderByType("audio/mp4a-latm"); codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); codec.start(); ``` 2. 编码音频数据 ``` ByteBuffer[] inputBuffers = codec.getInputBuffers(); ByteBuffer[] outputBuffers = codec.getOutputBuffers(); MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); boolean isInputDone = false; boolean isOutputDone = false; while (!isOutputDone) { if (!isInputDone) { int inputBufferIndex = codec.dequeueInputBuffer(timeoutUs); if (inputBufferIndex >= 0) { ByteBuffer inputBuffer = inputBuffers[inputBufferIndex]; inputBuffer.clear(); int bytesRead = audioInputStream.read(inputBuffer.array(), inputBuffer.arrayOffset() + inputBuffer.position(), inputBuffer.remaining()); if (bytesRead == -1) { codec.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); isInputDone = true; } else { codec.queueInputBuffer(inputBufferIndex, 0, bytesRead, 0, 0); } } } int outputBufferIndex = codec.dequeueOutputBuffer(bufferInfo, timeoutUs); if (outputBufferIndex >= 0) { ByteBuffer outputBuffer = outputBuffers[outputBufferIndex]; byte[] outData = new byte[bufferInfo.size]; outputBuffer.get(outData); outputBuffer.clear(); // 处理编码后的音频数据 codec.releaseOutputBuffer(outputBufferIndex, false); if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { isOutputDone = true; } } } ``` 3. 停止编码器 ``` codec.stop(); codec.release(); ``` 注意:上述代码中的`audioInputStream`是一个输入音频数据的流。`sampleRate`、`channelCount`、`bitRate`和`bufferSize`分别表示采样率、通道数、比特率和缓冲区大小。`timeoutUs`表示等待时间,可以设置为0表示立即返回。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值