Android进阶之视频压缩

视频压缩是一个有关视频类项目必不可少的环节,选择一个合适且稳定的压缩工具更是领开发者比较头疼的一件事情,网上压缩工具比比皆是,一旦入坑,如果出问题后期出现问题,各种成本更是令人畏惧,这篇文章或许可以让你少走一些“弯路”。
首先这里的视频压缩使用的是 VideoProcessor 介意者勿扰~,并且是音视频类实战项目长期稳定之后才写的此文章,压缩比基本保持在 7:3 左右。

接下来开始实战使用,以及遇到的问题。

1.导入依赖

com.github.yellowcath:VideoProcessor:2.4.2

2.调用方法

VideoProcessor.processor(mPresenter)
              .input(url)
              .outWidth(1600)
              .outHeight(1200)
              .output(outputUrl)
              .bitrate(mBitrate)
              .frameRate(10)
              .process()

方法介绍

.processor(mPresenter) - mPresenter 传入当前引用
.input(url) - url本地视频地址
.outWidth(1600) - 压缩后的宽度
.outHeight(1200) - 压缩后的高度
.output(outputUrl) - 压缩后的地址
.bitrate(mBitrate) - 比特率
.frameRate(10) - 帧速率

比特率会影响到压缩视频之后的效果,可以动态去设置比特率和帧速率去调整压缩效果

//默认三百万,有数据后拿数据的百分之四十
  var mBitrate = 3000000
  try {
         //拿到视频的比特率
         var media = MediaMetadataRetriever()
         media.setDataSource(locationVideoUrl)
         val extractMetadata =
               media.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE)
         Log.e(ContentValues.TAG, "当前视频的大小242412412 比特率->:${extractMetadata}")
         if (extractMetadata != null && extractMetadata.isNotEmpty()) {
                mBitrate = (extractMetadata.toInt() * 0.4).toInt()
          }
        } catch (e: Exception) {
           e.printStackTrace()
        }

以上就是压缩视频的使用步骤
以下是出现的问题

首先上述代码中很明显的错误就是压缩后的宽高是写死的,这样当用户传入不同形状的大小肯定会变形,所以我们可以根据原视频宽高进行压缩

基本我们会去本地拿资源会经过 onActivityResult 回调并且拿到 Intent data
可以通过

    public static List<LocalMedia> obtainMultipleResult(Intent data) {
        List<LocalMedia> result = new ArrayList<>();
        if (data != null) {
            result = (List<LocalMedia>) data.getSerializableExtra(PictureConfig.EXTRA_RESULT_SELECTION);
            if (result == null) {
                result = new ArrayList<>();
            }
            return result;
        }
        return result;
    }

LocalMedia

public class LocalMedia implements Parcelable {
    private String path;
    private String compressPath;
    private String cutPath;
    private long duration;
    private boolean isChecked;
    private boolean isCut;
    public int position;
    private int num;
    private int mimeType;
    private String pictureType;
    private boolean compressed;
    private int width;
    private int height;


    public String ossUrl;//记录上传成功后的图片地址
    public boolean isFail;//新增业务字段 是否是违规

    public LocalMedia() {

    }

    public LocalMedia(String path, long duration, int mimeType, String pictureType) {
        this.path = path;
        this.duration = duration;
        this.mimeType = mimeType;
        this.pictureType = pictureType;
    }

    public LocalMedia(String path, long duration, int mimeType, String pictureType, int width, int height) {
        this.path = path;
        this.duration = duration;
        this.mimeType = mimeType;
        this.pictureType = pictureType;
        this.width = width;
        this.height = height;
    }

    public LocalMedia(String path, long duration,
                      boolean isChecked, int position, int num, int mimeType) {
        this.path = path;
        this.duration = duration;
        this.isChecked = isChecked;
        this.position = position;
        this.num = num;
        this.mimeType = mimeType;
    }

    public String getPictureType() {
        if (TextUtils.isEmpty(pictureType)) {
            pictureType = "image/jpeg";
        }
        return pictureType;
    }

    public void setPictureType(String pictureType) {
        this.pictureType = pictureType;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public String getCompressPath() {
        return compressPath;
    }

    public void setCompressPath(String compressPath) {
        this.compressPath = compressPath;
    }

    public String getCutPath() {
        return cutPath;
    }

    public void setCutPath(String cutPath) {
        this.cutPath = cutPath;
    }

    public long getDuration() {
        return duration;
    }

    public void setDuration(long duration) {
        this.duration = duration;
    }


    public boolean isChecked() {
        return isChecked;
    }

    public void setChecked(boolean checked) {
        isChecked = checked;
    }

    public boolean isCut() {
        return isCut;
    }

    public void setCut(boolean cut) {
        isCut = cut;
    }

    public int getPosition() {
        return position;
    }

    public void setPosition(int position) {
        this.position = position;
    }

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }

    public int getMimeType() {
        return mimeType;
    }

    public void setMimeType(int mimeType) {
        this.mimeType = mimeType;
    }

    public boolean isCompressed() {
        return compressed;
    }

    public void setCompressed(boolean compressed) {
        this.compressed = compressed;
    }

    public int getWidth() {
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.path);
        dest.writeString(this.compressPath);
        dest.writeString(this.cutPath);
        dest.writeLong(this.duration);
        dest.writeByte(this.isChecked ? (byte) 1 : (byte) 0);
        dest.writeByte(this.isCut ? (byte) 1 : (byte) 0);
        dest.writeInt(this.position);
        dest.writeInt(this.num);
        dest.writeInt(this.mimeType);
        dest.writeString(this.pictureType);
        dest.writeByte(this.compressed ? (byte) 1 : (byte) 0);
        dest.writeInt(this.width);
        dest.writeInt(this.height);
    }

    protected LocalMedia(Parcel in) {
        this.path = in.readString();
        this.compressPath = in.readString();
        this.cutPath = in.readString();
        this.duration = in.readLong();
        this.isChecked = in.readByte() != 0;
        this.isCut = in.readByte() != 0;
        this.position = in.readInt();
        this.num = in.readInt();
        this.mimeType = in.readInt();
        this.pictureType = in.readString();
        this.compressed = in.readByte() != 0;
        this.width = in.readInt();
        this.height = in.readInt();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        LocalMedia that = (LocalMedia) o;
        return path != null && path.equals(that.path);
    }

    @Override
    public int hashCode() {
        if (path != null) {
            return path.hashCode();
        } else {
            return 0;
        }

    }

    public static final Parcelable.Creator<LocalMedia> CREATOR = new Parcelable.Creator<LocalMedia>() {
        @Override
        public LocalMedia createFromParcel(Parcel source) {
            return new LocalMedia(source);
        }

        @Override
        public LocalMedia[] newArray(int size) {
            return new LocalMedia[size];
        }
    };
}

将拿到的data 转成 LocalMedia 的集合,默认取第一个 result.get(0)
此时我们就拿到了LocalMedia 这里面有我们需要的宽、高、时间、本地路径等信息
此时我们就可以将上面代码改成

Int  videoWith = 1600
Int  videoHeight = 1200
if (videoMedia.width != null && videoMedia.width != 0){
          videoWith = videoMedia.width
   }
if (videoMedia.height != null && videoMedia.height != 0){
          videoHeight = videoMedia.height
   }

VideoProcessor.processor(mPresenter)
              .input(url)
              .outWidth(videoWith )
              .outHeight(videoHeight )
              .output(outputUrl)
              .bitrate(mBitrate)
              .frameRate(10)
              .process()

以上,视频压缩之后变形的问题就解决了

我们压缩之后会出现一个新的压缩后的路径,如果不及时删除,用户手机上就会多一个压缩之后的文件,会影响用户的使用体验,当我们使用之后要及时去删除压缩后的文件(这里不贴代码了,百度一大堆,如有需要请留言~)
删除的时候,部分机型会报错,此时需要注意了!安卓现在已经不允许对 /0 文件也就是系统默认文件进行操作了,所以我们设置的压缩后的路径不要放在 /0 目录下
可以这样

//压缩视频,使用完后别忘了把压缩后的视频删除掉
                    val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"
                    var outputUrl =
                        mPresenter.getExternalFilesDir("").toString() + "/Kome" + SimpleDateFormat(
                            FILENAME_FORMAT,
                            Locale.CHINA
                        ).format(System.currentTimeMillis()) + ".mp4"

这样基本都压缩步骤已经完成了,不出意外的话就要发版了,但是发上去之后就会发现部分机型会出现空指针的问题,经排查问题发生在底层源码里面 …processVideo()方法里面报错

可能最新版本已经修复此问题,如果没有可以直接重写 VideoProcessor 类,加一下防护,类似于 默认数据都是原有参数,尽量自己不要乱改

        int originWidth = 1600;
        if (retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH) != null){
            originWidth = Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
        }

最后贴上加防护后的VideoProcessor 代码

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.media.AudioFormat;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.media.MediaMetadataRetriever;
import android.media.MediaMuxer;
import android.media.MediaRecorder;
import android.net.Uri;
import android.util.Pair;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;

import static com.hw.videoprocessor.util.AudioUtil.getAudioBitrate;

import com.hw.videoprocessor.AudioProcessThread;
import com.hw.videoprocessor.VideoDecodeThread;
import com.hw.videoprocessor.VideoEncodeThread;
import com.hw.videoprocessor.VideoUtil;
import com.hw.videoprocessor.util.AudioFadeUtil;
import com.hw.videoprocessor.util.AudioUtil;
import com.hw.videoprocessor.util.CL;
import com.hw.videoprocessor.util.PcmToWavUtil;
import com.hw.videoprocessor.util.VideoMultiStepProgress;
import com.hw.videoprocessor.util.VideoProgressAve;
import com.hw.videoprocessor.util.VideoProgressListener;


@TargetApi(21)
public class VideoProcessor {
    final static String TAG = "VideoProcessor";
    final static String OUTPUT_MIME_TYPE = "video/avc";

    public static int DEFAULT_FRAME_RATE = 20;
    /**
     * 只有关键帧距为0的才能方便做逆序
     */
    public final static int DEFAULT_I_FRAME_INTERVAL = 1;

    public final static int DEFAULT_AAC_BITRATE = 192 * 1000;
    /**
     * 控制音频合成时,如果输入的音频文件长度不够,是否重复填充
     */
    public static boolean AUDIO_MIX_REPEAT = true;

    final static int TIMEOUT_USEC = 2500;


    public static void scaleVideo(Context context, Uri input, String output,
                                  int outWidth, int outHeight) throws Exception {
        processor(context)
                .input(input)
                .output(output)
                .outWidth(outWidth)
                .outHeight(outHeight)
                .process();
    }

    public static void cutVideo(Context context, Uri input, String output, int startTimeMs, int endTimeMs) throws Exception {
        processor(context)
                .input(input)
                .output(output)
                .startTimeMs(startTimeMs)
                .endTimeMs(endTimeMs)
                .process();
    }

    public static void changeVideoSpeed(Context context, Uri input, String output, float speed) throws Exception {
        processor(context)
                .input(input)
                .output(output)
                .speed(speed)
                .process();
    }


    /**
     * 对视频先检查,如果不是全关键帧,先处理成所有帧都是关键帧,再逆序
     */
    public static void reverseVideo(Context context, com.hw.videoprocessor.VideoProcessor.MediaSource input, String output, boolean reverseAudio, @Nullable VideoProgressListener listener) throws Exception {
        File tempFile = new File(context.getCacheDir(), System.currentTimeMillis() + ".temp");
        File temp2File = new File(context.getCacheDir(), System.currentTimeMillis() + ".temp2");
        try {
            MediaExtractor extractor = new MediaExtractor();
            input.setDataSource(extractor);
            int trackIndex = VideoUtil.selectTrack(extractor, false);
            extractor.selectTrack(trackIndex);
            int keyFrameCount = 0;
            int frameCount = 0;
            List<Long> frameTimeStamps = new ArrayList<>();
            while (true) {
                int flags = extractor.getSampleFlags();
                if (flags > 0 && (flags & MediaExtractor.SAMPLE_FLAG_SYNC) != 0) {
                    keyFrameCount++;
                }
                long sampleTime = extractor.getSampleTime();
                if (sampleTime < 0) {
                    break;
                }
                frameTimeStamps.add(sampleTime);
                frameCount++;
                extractor.advance();
            }
            extractor.release();

            if (frameCount == keyFrameCount || frameCount == keyFrameCount + 1) {
                reverseVideoNoDecode(input, output, reverseAudio, frameTimeStamps, listener);
            } else {
                VideoMultiStepProgress stepProgress = new VideoMultiStepProgress(new float[]{0.45f, 0.1f, 0.45f}, listener);
                stepProgress.setCurrentStep(0);
                float bitrateMultiple = (frameCount - keyFrameCount) / (float) keyFrameCount + 1;
                MediaMetadataRetriever retriever = new MediaMetadataRetriever();
                input.setDataSource(retriever);
                int oriBitrate = Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE));
                int duration = Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
                try {
                    processor(context)
                            .input(input)
                            .output(tempFile.getAbsolutePath())
                            .bitrate((int) (oriBitrate * bitrateMultiple))
                            .iFrameInterval(0)
                            .progressListener(stepProgress)
                            .process();
                } catch (MediaCodec.CodecException e) {
                    CL.e(e);
                    /** Nexus5上-1代表全关键帧*/
                    processor(context)
                            .input(input)
                            .output(tempFile.getAbsolutePath())
                            .bitrate((int) (oriBitrate * bitrateMultiple))
                            .iFrameInterval(-1)
                            .progressListener(stepProgress)
                            .process();
                }
                stepProgress.setCurrentStep(1);
                reverseVideoNoDecode(new com.hw.videoprocessor.VideoProcessor.MediaSource(tempFile.getAbsolutePath()), temp2File.getAbsolutePath(), reverseAudio, null, stepProgress);
                int oriIFrameInterval = (int) (keyFrameCount / (duration / 1000f));
                oriIFrameInterval = oriIFrameInterval == 0 ? 1 : oriIFrameInterval;
                stepProgress.setCurrentStep(2);
                processor(context)
                        .input(temp2File.getAbsolutePath())
                        .output(output)
                        .bitrate(oriBitrate)
                        .iFrameInterval(oriIFrameInterval)
                        .progressListener(stepProgress)
                        .process();
            }
        } finally {
            tempFile.delete();
            temp2File.delete();
        }
    }

    /**
     * 支持裁剪缩放快慢放
     */
    public static void processVideo(@NotNull Context context, @NotNull Processor processor) throws Exception {

        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
        processor.input.setDataSource(retriever);
        int originWidth = 1600;
        if (retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH) != null){
            originWidth = Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
        }
        int originHeight = 1200;
        if (retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT) != null){
            originHeight = Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
        }
        int rotationValue = 270;
        if (retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION) != null){
            rotationValue = Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION)); //270
        }
        int oriBitrate = 8338866;
        if (retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE) != null){
            oriBitrate = Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE)); //8338866
        }
        int durationMs = 15111;
        if (retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION) != null){
            durationMs = Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)); //15111
        }
        retriever.release();
        if (processor.bitrate == null) {
            processor.bitrate = oriBitrate;
        }
        if (processor.iFrameInterval == null) {
            processor.iFrameInterval = DEFAULT_I_FRAME_INTERVAL;
        }

        int resultWidth = processor.outWidth == null ? originWidth : processor.outWidth;
        int resultHeight = processor.outHeight == null ? originHeight : processor.outHeight;
        resultWidth = resultWidth % 2 == 0 ? resultWidth : resultWidth + 1;
        resultHeight = resultHeight % 2 == 0 ? resultHeight : resultHeight + 1;

        if (rotationValue == 90 || rotationValue == 270) {
            int temp = resultHeight;
            resultHeight = resultWidth;
            resultWidth = temp;
        }

        MediaExtractor extractor = new MediaExtractor();
        processor.input.setDataSource(extractor);
        int videoIndex = VideoUtil.selectTrack(extractor, false);
        int audioIndex = VideoUtil.selectTrack(extractor, true);
        MediaMuxer mediaMuxer = new MediaMuxer(processor.output, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
        int muxerAudioTrackIndex = 0;
        boolean shouldChangeAudioSpeed = processor.changeAudioSpeed == null ? true : processor.changeAudioSpeed;
        Integer audioEndTimeMs = processor.endTimeMs;
        if (audioIndex >= 0) {
            MediaFormat audioTrackFormat = extractor.getTrackFormat(audioIndex);
            String audioMimeType = MediaFormat.MIMETYPE_AUDIO_AAC;
            int bitrate = getAudioBitrate(audioTrackFormat);
            int channelCount = audioTrackFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
            int sampleRate = audioTrackFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
            int maxBufferSize = AudioUtil.getAudioMaxBufferSize(audioTrackFormat);
            MediaFormat audioEncodeFormat = MediaFormat.createAudioFormat(audioMimeType, sampleRate, channelCount);//参数对应-> mime type、采样率、声道数
            audioEncodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);//比特率
            audioEncodeFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
            audioEncodeFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, maxBufferSize);

            if (shouldChangeAudioSpeed) {
                if (processor.startTimeMs != null || processor.endTimeMs != null || processor.speed != null) {
                    long durationUs = audioTrackFormat.getLong(MediaFormat.KEY_DURATION);
                    if (processor.startTimeMs != null && processor.endTimeMs != null) {
                        durationUs = (processor.endTimeMs - processor.startTimeMs) * 1000;
                    }
                    if (processor.speed != null) {
                        durationUs /= processor.speed;
                    }
                    audioEncodeFormat.setLong(MediaFormat.KEY_DURATION, durationUs);
                }
            } else {
                long videoDurationUs = durationMs * 1000;
                long audioDurationUs = audioTrackFormat.getLong(MediaFormat.KEY_DURATION);

                if (processor.startTimeMs != null || processor.endTimeMs != null || processor.speed != null) {
                    if (processor.startTimeMs != null && processor.endTimeMs != null) {
                        videoDurationUs = (processor.endTimeMs - processor.startTimeMs) * 1000;
                    }
                    if (processor.speed != null) {
                        videoDurationUs /= processor.speed;
                    }
                    long avDurationUs = videoDurationUs < audioDurationUs ? videoDurationUs : audioDurationUs;
                    audioEncodeFormat.setLong(MediaFormat.KEY_DURATION, avDurationUs);
                    audioEndTimeMs = (processor.startTimeMs == null ? 0 : processor.startTimeMs) + (int) (avDurationUs / 1000);
                }
            }

            AudioUtil.checkCsd(audioEncodeFormat,
                    MediaCodecInfo.CodecProfileLevel.AACObjectLC,
                    sampleRate,
                    channelCount
            );
            //提前推断出音頻格式加到MeidaMuxer,不然实际上应该到音频预处理完才能addTrack,会卡住视频编码的进度
            muxerAudioTrackIndex = mediaMuxer.addTrack(audioEncodeFormat);
        }
        extractor.selectTrack(videoIndex);
        if (processor.startTimeMs != null) {
            extractor.seekTo(processor.startTimeMs * 1000, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
        } else {
            extractor.seekTo(0, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
        }

        VideoProgressAve progressAve = new VideoProgressAve(processor.listener);
        progressAve.setSpeed(processor.speed);
        progressAve.setStartTimeMs(processor.startTimeMs == null ? 0 : processor.startTimeMs);
        progressAve.setEndTimeMs(processor.endTimeMs == null ? durationMs : processor.endTimeMs);
        AtomicBoolean decodeDone = new AtomicBoolean(false);
        CountDownLatch muxerStartLatch = new CountDownLatch(1);
        VideoEncodeThread encodeThread = new VideoEncodeThread(extractor, mediaMuxer,processor.bitrate,
                resultWidth, resultHeight, processor.iFrameInterval, processor.frameRate == null ? DEFAULT_FRAME_RATE : processor.frameRate, videoIndex,
                decodeDone, muxerStartLatch);
        int srcFrameRate = VideoUtil.getFrameRate(processor.input);
        if (srcFrameRate <= 0) {
            srcFrameRate = (int) Math.ceil(VideoUtil.getAveFrameRate(processor.input));
        }
        VideoDecodeThread decodeThread = new VideoDecodeThread(encodeThread, extractor, processor.startTimeMs, processor.endTimeMs, srcFrameRate,
                processor.frameRate == null ? DEFAULT_FRAME_RATE : processor.frameRate, processor.speed, processor.dropFrames, videoIndex, decodeDone);

        AudioProcessThread audioProcessThread = new AudioProcessThread(context, processor.input, mediaMuxer, processor.startTimeMs, audioEndTimeMs,
                shouldChangeAudioSpeed ? processor.speed : null, muxerAudioTrackIndex, muxerStartLatch);
        encodeThread.setProgressAve(progressAve);
        audioProcessThread.setProgressAve(progressAve);
        decodeThread.start();
        encodeThread.start();
        audioProcessThread.start();
        try {
            long s = System.currentTimeMillis();
            decodeThread.join();
            encodeThread.join();
            long e1 = System.currentTimeMillis();
            audioProcessThread.join();
            long e2 = System.currentTimeMillis();
            CL.w(String.format("编解码:%dms,音频:%dms", e1 - s, e2 - s));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        try {
            mediaMuxer.release();
            extractor.release();
        } catch (Exception e2) {
            CL.e(e2);
        }
        if (encodeThread.getException() != null) {
            throw encodeThread.getException();
        } else if (decodeThread.getException() != null) {
            throw decodeThread.getException();
        } else if (audioProcessThread.getException() != null) {
            throw audioProcessThread.getException();
        }
    }

    public static void reverseVideoNoDecode(com.hw.videoprocessor.VideoProcessor.MediaSource input, String output, boolean reverseAudio) throws IOException {
        reverseVideoNoDecode(input, output, reverseAudio, null, null);
    }

    /**
     * 直接对视频进行逆序,用于所有帧都是关键帧的情况
     */
    @SuppressLint("WrongConstant")
    public static void reverseVideoNoDecode(com.hw.videoprocessor.VideoProcessor.MediaSource input, String output, boolean reverseAudio, List<Long> videoFrameTimeStamps, @Nullable VideoProgressListener listener) throws IOException {
        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
        input.setDataSource(retriever);
        int durationMs = Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
        retriever.release();

        MediaExtractor extractor = new MediaExtractor();
        input.setDataSource(extractor);

        int videoTrackIndex = VideoUtil.selectTrack(extractor, false);
        int audioTrackIndex = VideoUtil.selectTrack(extractor, true);
        boolean audioExist = audioTrackIndex >= 0;

        final int MIN_FRAME_INTERVAL = 10 * 1000;
        MediaMuxer mediaMuxer = new MediaMuxer(output, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
        extractor.selectTrack(videoTrackIndex);
        MediaFormat videoTrackFormat = extractor.getTrackFormat(videoTrackIndex);
        long videoDurationUs = videoTrackFormat.getLong(MediaFormat.KEY_DURATION);
        long audioDurationUs = 0;
        int videoMuxerTrackIndex = mediaMuxer.addTrack(videoTrackFormat);
        int audioMuxerTrackIndex = 0;
        if (audioExist) {
            MediaFormat audioTrackFormat = extractor.getTrackFormat(audioTrackIndex);
            audioMuxerTrackIndex = mediaMuxer.addTrack(audioTrackFormat);
            audioDurationUs = audioTrackFormat.getLong(MediaFormat.KEY_DURATION);
        }
        mediaMuxer.start();
        int maxBufferSize = videoTrackFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
        ByteBuffer buffer = ByteBuffer.allocateDirect(maxBufferSize);
        VideoUtil.seekToLastFrame(extractor, videoTrackIndex, durationMs);
        long lastFrameTimeUs = -1;
        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
        try {
            //写视频帧
            if (videoFrameTimeStamps != null && videoFrameTimeStamps.size() > 0) {
                for (int i = videoFrameTimeStamps.size() - 1; i >= 0; i--) {
                    extractor.seekTo(videoFrameTimeStamps.get(i), MediaExtractor.SEEK_TO_CLOSEST_SYNC);
                    long sampleTime = extractor.getSampleTime();
                    if (lastFrameTimeUs == -1) {
                        lastFrameTimeUs = sampleTime;
                    }
                    info.presentationTimeUs = lastFrameTimeUs - sampleTime;
                    info.size = extractor.readSampleData(buffer, 0);
                    info.flags = extractor.getSampleFlags();

                    if (info.size < 0) {
                        break;
                    }
                    mediaMuxer.writeSampleData(videoMuxerTrackIndex, buffer, info);
                    if (listener != null) {
                        float videoProgress = info.presentationTimeUs / (float) videoDurationUs;
                        videoProgress = videoProgress > 1 ? 1 : videoProgress;
                        videoProgress *= 0.7f;
                        listener.onProgress(videoProgress);
                    }
                }
            } else {
                while (true) {
                    long sampleTime = extractor.getSampleTime();
                    if (lastFrameTimeUs == -1) {
                        lastFrameTimeUs = sampleTime;
                    }
                    info.presentationTimeUs = lastFrameTimeUs - sampleTime;
                    info.size = extractor.readSampleData(buffer, 0);
                    info.flags = extractor.getSampleFlags();

                    if (info.size < 0) {
                        break;
                    }
                    mediaMuxer.writeSampleData(videoMuxerTrackIndex, buffer, info);
                    if (listener != null) {
                        float videoProgress = info.presentationTimeUs / (float) videoDurationUs;
                        videoProgress = videoProgress > 1 ? 1 : videoProgress;
                        videoProgress *= 0.7f;
                        listener.onProgress(videoProgress);
                    }
                    long seekTime = sampleTime - MIN_FRAME_INTERVAL;
                    if (seekTime <= 0) {
                        break;
                    }
                    extractor.seekTo(seekTime, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
                }
            }
            //写音频帧
            if (audioExist) {
                extractor.unselectTrack(videoTrackIndex);
                extractor.selectTrack(audioTrackIndex);
                if (reverseAudio) {
                    List<Long> audioFrameStamps = getFrameTimeStampsList(extractor);
                    lastFrameTimeUs = -1;
                    for (int i = audioFrameStamps.size() - 1; i >= 0; i--) {
                        extractor.seekTo(audioFrameStamps.get(i), MediaExtractor.SEEK_TO_CLOSEST_SYNC);
                        long sampleTime = extractor.getSampleTime();
                        if (lastFrameTimeUs == -1) {
                            lastFrameTimeUs = sampleTime;
                        }
                        info.presentationTimeUs = lastFrameTimeUs - sampleTime;
                        info.size = extractor.readSampleData(buffer, 0);
                        info.flags = extractor.getSampleFlags();
                        if (info.size < 0) {
                            break;
                        }
                        mediaMuxer.writeSampleData(audioMuxerTrackIndex, buffer, info);
                        if (listener != null) {
                            float audioProgress = info.presentationTimeUs / (float) audioDurationUs;
                            audioProgress = audioProgress > 1 ? 1 : audioProgress;
                            audioProgress = 0.7f + audioProgress * 0.3f;
                            listener.onProgress(audioProgress);
                        }
                    }
                } else {
                    extractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
                    while (true) {
                        long sampleTime = extractor.getSampleTime();
                        if (sampleTime == -1) {
                            break;
                        }
                        info.presentationTimeUs = sampleTime;
                        info.size = extractor.readSampleData(buffer, 0);
                        info.flags = extractor.getSampleFlags();
                        if (info.size < 0) {
                            break;
                        }
                        mediaMuxer.writeSampleData(audioMuxerTrackIndex, buffer, info);
                        if (listener != null) {
                            float audioProgress = info.presentationTimeUs / (float) audioDurationUs;
                            audioProgress = audioProgress > 1 ? 1 : audioProgress;
                            audioProgress = 0.7f + audioProgress * 0.3f;
                            listener.onProgress(audioProgress);
                        }
                        extractor.advance();
                    }
                }
            }
            if (listener != null) {
                listener.onProgress(1f);
            }
        } catch (Exception e) {
            CL.e(e);
        } finally {
            extractor.release();
            mediaMuxer.release();
        }
    }

    static List<Long> getFrameTimeStampsList(MediaExtractor extractor){
        List<Long> frameTimeStamps = new ArrayList<>();
        while (true) {
            long sampleTime = extractor.getSampleTime();
            if (sampleTime < 0) {
                break;
            }
            frameTimeStamps.add(sampleTime);
            extractor.advance();
        }
        return frameTimeStamps;
    }

    /**
     * 不需要改变音频速率的情况下,直接读写就可
     * 只支持16bit音频
     *
     * @param videoVolume 0静音,100表示原音
     */
    @SuppressLint("WrongConstant")
    public static void adjustVideoVolume(Context context, final com.hw.videoprocessor.VideoProcessor.MediaSource mediaSource, final String output, int videoVolume, float faceInSec, float fadeOutSec) throws IOException {
        if (videoVolume == 100 && faceInSec == 0f && fadeOutSec == 0f) {
            AudioUtil.copyFile(String.valueOf(mediaSource), output);
            return;
        }
        File cacheDir = new File(context.getCacheDir(), "pcm");
        cacheDir.mkdir();

        MediaExtractor oriExtrator = new MediaExtractor();
        mediaSource.setDataSource(oriExtrator);
        int oriAudioIndex = VideoUtil.selectTrack(oriExtrator, true);
        if (oriAudioIndex < 0) {
            CL.e("no audio stream!");
            AudioUtil.copyFile(String.valueOf(mediaSource), output);
            return;
        }
        long time = System.currentTimeMillis();
        final File videoPcmFile = new File(cacheDir, "video_" + time + ".pcm");
        final File videoPcmAdjustedFile = new File(cacheDir, "video_" + time + "_adjust.pcm");
        final File videoWavFile = new File(cacheDir, "video_" + time + ".wav");

        AudioUtil.decodeToPCM(mediaSource, videoPcmFile.getAbsolutePath(), null, null);
        AudioUtil.adjustPcmVolume(videoPcmFile.getAbsolutePath(), videoPcmAdjustedFile.getAbsolutePath(), videoVolume);

        MediaFormat audioTrackFormat = oriExtrator.getTrackFormat(oriAudioIndex);
        final int sampleRate = audioTrackFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
        int channelCount = audioTrackFormat.containsKey(MediaFormat.KEY_CHANNEL_COUNT) ? audioTrackFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT) : 1;
        int channelConfig = AudioFormat.CHANNEL_IN_MONO;
        if (channelCount == 2) {
            channelConfig = AudioFormat.CHANNEL_IN_STEREO;
        }
        if (faceInSec > 0 || fadeOutSec > 0) {
            AudioFadeUtil.audioFade(videoPcmAdjustedFile.getAbsolutePath(), sampleRate, channelCount, faceInSec, fadeOutSec);
        }
        new PcmToWavUtil(sampleRate, channelConfig, channelCount, AudioFormat.ENCODING_PCM_16BIT).pcmToWav(videoPcmAdjustedFile.getAbsolutePath(), videoWavFile.getAbsolutePath());

        final int TIMEOUT_US = 2500;
        //重新将速率变化过后的pcm写入
        int audioBitrate = getAudioBitrate(audioTrackFormat);

        int oriVideoIndex = VideoUtil.selectTrack(oriExtrator, false);
        MediaFormat oriVideoFormat = oriExtrator.getTrackFormat(oriVideoIndex);
        int rotation = oriVideoFormat.containsKey(MediaFormat.KEY_ROTATION) ? oriVideoFormat.getInteger(MediaFormat.KEY_ROTATION) : 0;
        MediaMuxer mediaMuxer = new MediaMuxer(output, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
        mediaMuxer.setOrientationHint(rotation);
        int muxerVideoIndex = mediaMuxer.addTrack(oriVideoFormat);
        int muxerAudioIndex = mediaMuxer.addTrack(audioTrackFormat);

        //重新写入音频
        mediaMuxer.start();

        MediaExtractor pcmExtrator = new MediaExtractor();
        pcmExtrator.setDataSource(videoWavFile.getAbsolutePath());
        int audioTrack = VideoUtil.selectTrack(pcmExtrator, true);
        pcmExtrator.selectTrack(audioTrack);
        MediaFormat pcmTrackFormat = pcmExtrator.getTrackFormat(audioTrack);
        int maxBufferSize = AudioUtil.getAudioMaxBufferSize(pcmTrackFormat);
        ByteBuffer buffer = ByteBuffer.allocateDirect(maxBufferSize);
        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
        MediaFormat encodeFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, sampleRate, channelCount);//参数对应-> mime type、采样率、声道数
        encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, audioBitrate);//比特率
        encodeFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
        encodeFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, maxBufferSize);
        MediaCodec encoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
        encoder.configure(encodeFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        encoder.start();
        boolean encodeInputDone = false;
        boolean encodeDone = false;
        long lastAudioFrameTimeUs = -1;
        final int AAC_FRAME_TIME_US = 1024 * 1000 * 1000 / sampleRate;
        boolean detectTimeError = false;
        try {
            while (!encodeDone) {
                int inputBufferIndex = encoder.dequeueInputBuffer(TIMEOUT_US);
                if (!encodeInputDone && inputBufferIndex >= 0) {
                    long sampleTime = pcmExtrator.getSampleTime();
                    if (sampleTime < 0) {
                        encodeInputDone = true;
                        encoder.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                    } else {
                        int flags = pcmExtrator.getSampleFlags();
                        buffer.clear();
                        int size = pcmExtrator.readSampleData(buffer, 0);
                        ByteBuffer inputBuffer = encoder.getInputBuffer(inputBufferIndex);
                        inputBuffer.clear();
                        inputBuffer.put(buffer);
                        inputBuffer.position(0);
                        CL.it(TAG, "audio queuePcmBuffer " + sampleTime / 1000 + " size:" + size);
                        encoder.queueInputBuffer(inputBufferIndex, 0, size, sampleTime, flags);
                        pcmExtrator.advance();
                    }
                }

                while (true) {
                    int outputBufferIndex = encoder.dequeueOutputBuffer(info, TIMEOUT_US);
                    if (outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
                        break;
                    } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                        MediaFormat newFormat = encoder.getOutputFormat();
                        CL.it(TAG, "audio decode newFormat = " + newFormat);
                    } else if (outputBufferIndex < 0) {
                        //ignore
                        CL.et(TAG, "unexpected result from audio decoder.dequeueOutputBuffer: " + outputBufferIndex);
                    } else {
                        if (info.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
                            encodeDone = true;
                            break;
                        }
                        ByteBuffer encodeOutputBuffer = encoder.getOutputBuffer(outputBufferIndex);
                        CL.it(TAG, "audio writeSampleData " + info.presentationTimeUs + " size:" + info.size + " flags:" + info.flags);
                        if (!detectTimeError && lastAudioFrameTimeUs != -1 && info.presentationTimeUs < lastAudioFrameTimeUs + AAC_FRAME_TIME_US) {
                            //某些情况下帧时间会出错,目前未找到原因(系统相机录得双声道视频正常,我录的单声道视频不正常)
                            CL.et(TAG, "audio 时间戳错误,lastAudioFrameTimeUs:" + lastAudioFrameTimeUs + " " +
                                    "info.presentationTimeUs:" + info.presentationTimeUs);
                            detectTimeError = true;
                        }
                        if (detectTimeError) {
                            info.presentationTimeUs = lastAudioFrameTimeUs + AAC_FRAME_TIME_US;
                            CL.et(TAG, "audio 时间戳错误,使用修正的时间戳:" + info.presentationTimeUs);
                            detectTimeError = false;
                        }
                        if (info.flags != MediaCodec.BUFFER_FLAG_CODEC_CONFIG) {
                            lastAudioFrameTimeUs = info.presentationTimeUs;
                        }
                        mediaMuxer.writeSampleData(muxerAudioIndex, encodeOutputBuffer, info);

                        encodeOutputBuffer.clear();
                        encoder.releaseOutputBuffer(outputBufferIndex, false);
                    }
                }
            }
            //重新将视频写入
            if (oriAudioIndex >= 0) {
                oriExtrator.unselectTrack(oriAudioIndex);
            }
            oriExtrator.selectTrack(oriVideoIndex);
            oriExtrator.seekTo(0, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
            maxBufferSize = oriVideoFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
            int frameRate = oriVideoFormat.containsKey(MediaFormat.KEY_FRAME_RATE) ? oriVideoFormat.getInteger(MediaFormat.KEY_FRAME_RATE) : (int) Math.ceil(VideoUtil.getAveFrameRate(mediaSource));
            buffer = ByteBuffer.allocateDirect(maxBufferSize);
            final int VIDEO_FRAME_TIME_US = (int) (1000 * 1000f / frameRate);
            long lastVideoFrameTimeUs = -1;
            detectTimeError = false;
            while (true) {
                long sampleTimeUs = oriExtrator.getSampleTime();
                if (sampleTimeUs == -1) {
                    break;
                }
                info.presentationTimeUs = sampleTimeUs;
                info.flags = oriExtrator.getSampleFlags();
                info.size = oriExtrator.readSampleData(buffer, 0);
                if (info.size < 0) {
                    break;
                }
                //写入视频
                if (!detectTimeError && lastVideoFrameTimeUs != -1 && info.presentationTimeUs < lastVideoFrameTimeUs + VIDEO_FRAME_TIME_US) {
                    //某些视频帧时间会出错
                    CL.et(TAG, "video 时间戳错误,lastVideoFrameTimeUs:" + lastVideoFrameTimeUs + " " +
                            "info.presentationTimeUs:" + info.presentationTimeUs + " VIDEO_FRAME_TIME_US:" + VIDEO_FRAME_TIME_US);
                    detectTimeError = true;
                }
                if (detectTimeError) {
                    info.presentationTimeUs = lastVideoFrameTimeUs + VIDEO_FRAME_TIME_US;
                    CL.et(TAG, "video 时间戳错误,使用修正的时间戳:" + info.presentationTimeUs);
                    detectTimeError = false;
                }
                if (info.flags != MediaCodec.BUFFER_FLAG_CODEC_CONFIG) {
                    lastVideoFrameTimeUs = info.presentationTimeUs;
                }
                CL.wt(TAG, "video writeSampleData:" + info.presentationTimeUs + " type:" + info.flags + " size:" + info.size);
                mediaMuxer.writeSampleData(muxerVideoIndex, buffer, info);
                oriExtrator.advance();
            }
        } finally {
            videoPcmFile.delete();
            videoPcmAdjustedFile.delete();
            videoWavFile.delete();

            try {
                pcmExtrator.release();
                oriExtrator.release();
                mediaMuxer.release();
                encoder.stop();
                encoder.release();
            } catch (Exception e) {
                CL.e(e);
            }
        }
    }

    /**
     * 不需要改变音频速率的情况下,直接读写就可
     * 只支持16bit音频
     *
     * @param videoVolume 0静音,100表示原音
     * @param aacVolume   0静音,100表示原音
     */
    @SuppressLint("WrongConstant")
    public static void mixAudioTrack(Context context, final com.hw.videoprocessor.VideoProcessor.MediaSource videoInput, final com.hw.videoprocessor.VideoProcessor.MediaSource audioInput, final String output,
                                     Integer startTimeMs, Integer endTimeMs,
                                     int videoVolume,
                                     int aacVolume,
                                     float fadeInSec, float fadeOutSec) throws IOException {
        File cacheDir = new File(context.getCacheDir(), "pcm");
        cacheDir.mkdir();

        final File videoPcmFile = new File(cacheDir, "video_" + System.currentTimeMillis() + ".pcm");
        File aacPcmFile = new File(cacheDir, "aac_" + System.currentTimeMillis() + ".pcm");

        final Integer startTimeUs = startTimeMs == null ? 0 : startTimeMs * 1000;
        final Integer endTimeUs = endTimeMs == null ? null : endTimeMs * 1000;
        final int videoDurationMs;
        if (endTimeUs == null) {
            MediaMetadataRetriever retriever = new MediaMetadataRetriever();
            videoInput.setDataSource(retriever);
            videoDurationMs = Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
        } else {
            videoDurationMs = (endTimeUs - startTimeUs) / 1000;
        }
        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
        audioInput.setDataSource(retriever);
        final int aacDurationMs = Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
        retriever.release();

        MediaExtractor oriExtrator = new MediaExtractor();
        videoInput.setDataSource(oriExtrator);
        int oriAudioIndex = VideoUtil.selectTrack(oriExtrator, true);
        MediaExtractor audioExtractor = new MediaExtractor();
        audioInput.setDataSource(audioExtractor);
        int aacAudioIndex = VideoUtil.selectTrack(audioExtractor, true);
        File wavFile;
        int sampleRate;
        File adjustedPcm;
        int channelCount;
        int audioBitrate;
        final int TIMEOUT_US = 2500;
        //重新将速率变化过后的pcm写入
        int oriVideoIndex = VideoUtil.selectTrack(oriExtrator, false);
        MediaFormat oriVideoFormat = oriExtrator.getTrackFormat(oriVideoIndex);
        int rotation = oriVideoFormat.containsKey(MediaFormat.KEY_ROTATION) ? oriVideoFormat.getInteger(MediaFormat.KEY_ROTATION) : 0;
        MediaMuxer mediaMuxer = new MediaMuxer(output, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
        mediaMuxer.setOrientationHint(rotation);
        int muxerVideoIndex = mediaMuxer.addTrack(oriVideoFormat);
        int muxerAudioIndex;
        if (oriAudioIndex >= 0) {
            long s1 = System.currentTimeMillis();
            final CountDownLatch latch = new CountDownLatch(2);
            //音频转化为PCM
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        AudioUtil.decodeToPCM(videoInput, videoPcmFile.getAbsolutePath(), startTimeUs, endTimeUs);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    } finally {
                        latch.countDown();
                    }
                }
            }).start();
            final File finalAacPcmFile = aacPcmFile;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        AudioUtil.decodeToPCM(audioInput, finalAacPcmFile.getAbsolutePath(), 0, aacDurationMs > videoDurationMs ? videoDurationMs * 1000 : aacDurationMs * 1000);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    } finally {
                        latch.countDown();
                    }
                }
            }).start();
            try {
                latch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            long s2 = System.currentTimeMillis();


            //检查两段音频格式是否一致,不一致则统一转换为单声道+44100
            Pair<Integer, Integer> resultPair = AudioUtil.checkAndAdjustAudioFormat(videoPcmFile.getAbsolutePath(),
                    aacPcmFile.getAbsolutePath(),
                    oriExtrator.getTrackFormat(oriAudioIndex),
                    audioExtractor.getTrackFormat(aacAudioIndex)
            );
            channelCount = resultPair.first;
            sampleRate = resultPair.second;
            audioExtractor.release();
            long s3 = System.currentTimeMillis();

            //检查音频长度是否需要重复填充
            if (AUDIO_MIX_REPEAT) {
                aacPcmFile = AudioUtil.checkAndFillPcm(aacPcmFile, aacDurationMs, videoDurationMs);
            }

            //混合并调整音量
            adjustedPcm = new File(cacheDir, "adjusted_" + System.currentTimeMillis() + ".pcm");
            AudioUtil.mixPcm(videoPcmFile.getAbsolutePath(), aacPcmFile.getAbsolutePath(), adjustedPcm.getAbsolutePath()
                    , videoVolume, aacVolume);
            wavFile = new File(context.getCacheDir(), adjustedPcm.getName() + ".wav");
            long s4 = System.currentTimeMillis();

            int channelConfig = AudioFormat.CHANNEL_IN_MONO;
            if (channelCount == 2) {
                channelConfig = AudioFormat.CHANNEL_IN_STEREO;
            }
            //淡入淡出
            if (fadeInSec != 0 || fadeOutSec != 0) {
                AudioFadeUtil.audioFade(adjustedPcm.getAbsolutePath(), sampleRate, channelCount, fadeInSec, fadeOutSec);
            }
            //PCM转WAV
            new PcmToWavUtil(sampleRate, channelConfig, channelCount, AudioFormat.ENCODING_PCM_16BIT).pcmToWav(adjustedPcm.getAbsolutePath(), wavFile.getAbsolutePath());
            long s5 = System.currentTimeMillis();
            CL.et("hwLog", String.format("decode:%dms,resample:%dms,mix:%dms,fade:%dms", s2 - s1, s3 - s2, s4 - s3, s5 - s4));
            MediaFormat oriAudioFormat = oriExtrator.getTrackFormat(oriAudioIndex);
            audioBitrate = getAudioBitrate(oriAudioFormat);
            oriAudioFormat.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AAC);
            AudioUtil.checkCsd(oriAudioFormat,
                    MediaCodecInfo.CodecProfileLevel.AACObjectLC,
                    sampleRate,
                    channelCount
            );
            muxerAudioIndex = mediaMuxer.addTrack(oriAudioFormat);
        } else {
            AudioUtil.decodeToPCM(audioInput, aacPcmFile.getAbsolutePath(), 0,
                    aacDurationMs > videoDurationMs ? videoDurationMs * 1000 : aacDurationMs * 1000);
            MediaFormat audioTrackFormat = audioExtractor.getTrackFormat(aacAudioIndex);
            audioBitrate = getAudioBitrate(audioTrackFormat);
            channelCount = audioTrackFormat.containsKey(MediaFormat.KEY_CHANNEL_COUNT) ?
                    audioTrackFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT) : 1;
            sampleRate = audioTrackFormat.containsKey(MediaFormat.KEY_SAMPLE_RATE) ?
                    audioTrackFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE) : 44100;
            int channelConfig = AudioFormat.CHANNEL_IN_MONO;
            if (channelCount == 2) {
                channelConfig = AudioFormat.CHANNEL_IN_STEREO;
            }
            AudioUtil.checkCsd(audioTrackFormat,
                    MediaCodecInfo.CodecProfileLevel.AACObjectLC,
                    sampleRate,
                    channelCount);
            audioTrackFormat.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AAC);
            muxerAudioIndex = mediaMuxer.addTrack(audioTrackFormat);

            sampleRate = audioTrackFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
            channelCount = audioTrackFormat.containsKey(MediaFormat.KEY_CHANNEL_COUNT) ? audioTrackFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT) : 1;
            if (channelCount > 2) {
                File tempFile = new File(aacPcmFile + ".channel");
                AudioUtil.stereoToMonoSimple(aacPcmFile.getAbsolutePath(), tempFile.getAbsolutePath(), channelCount);
                channelCount = 1;
                aacPcmFile.delete();
                aacPcmFile = tempFile;
            }

            if (aacVolume != 50) {
                adjustedPcm = new File(cacheDir, "adjusted_" + System.currentTimeMillis() + ".pcm");
                AudioUtil.adjustPcmVolume(aacPcmFile.getAbsolutePath(), adjustedPcm.getAbsolutePath(), aacVolume);
            } else {
                adjustedPcm = aacPcmFile;
            }

            channelConfig = AudioFormat.CHANNEL_IN_MONO;
            if (channelCount == 2) {
                channelConfig = AudioFormat.CHANNEL_IN_STEREO;
            }
            wavFile = new File(context.getCacheDir(), adjustedPcm.getName() + ".wav");
            //淡入淡出
            if (fadeInSec != 0 || fadeOutSec != 0) {
                AudioFadeUtil.audioFade(adjustedPcm.getAbsolutePath(), sampleRate, channelCount, fadeInSec, fadeOutSec);
            }
            //PCM转WAV
            new PcmToWavUtil(sampleRate, channelConfig, channelCount, AudioFormat.ENCODING_PCM_16BIT).pcmToWav(adjustedPcm.getAbsolutePath(), wavFile.getAbsolutePath());
        }

        //重新写入音频
        mediaMuxer.start();

        MediaExtractor pcmExtrator = new MediaExtractor();
        pcmExtrator.setDataSource(wavFile.getAbsolutePath());
        int audioTrack = VideoUtil.selectTrack(pcmExtrator, true);
        pcmExtrator.selectTrack(audioTrack);
        MediaFormat pcmTrackFormat = pcmExtrator.getTrackFormat(audioTrack);
        int maxBufferSize = AudioUtil.getAudioMaxBufferSize(pcmTrackFormat);
        ByteBuffer buffer = ByteBuffer.allocateDirect(maxBufferSize);
        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
        MediaFormat encodeFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, sampleRate, channelCount);//参数对应-> mime type、采样率、声道数
        encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, audioBitrate);//比特率
        encodeFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
        encodeFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, maxBufferSize);
        MediaCodec encoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
        encoder.configure(encodeFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        encoder.start();
        boolean encodeInputDone = false;
        boolean encodeDone = false;
        long lastAudioFrameTimeUs = -1;
        final int AAC_FRAME_TIME_US = 1024 * 1000 * 1000 / sampleRate;
        boolean detectTimeError = false;
        try {
            while (!encodeDone) {
                int inputBufferIndex = encoder.dequeueInputBuffer(TIMEOUT_US);
                if (!encodeInputDone && inputBufferIndex >= 0) {
                    long sampleTime = pcmExtrator.getSampleTime();
                    if (sampleTime < 0) {
                        encodeInputDone = true;
                        encoder.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                    } else {
                        int flags = pcmExtrator.getSampleFlags();
                        buffer.clear();
                        int size = pcmExtrator.readSampleData(buffer, 0);
                        ByteBuffer inputBuffer = encoder.getInputBuffer(inputBufferIndex);
                        inputBuffer.clear();
                        inputBuffer.put(buffer);
                        inputBuffer.position(0);
                        CL.it(TAG, "audio queuePcmBuffer " + sampleTime / 1000 + " size:" + size);
                        encoder.queueInputBuffer(inputBufferIndex, 0, size, sampleTime, flags);
                        pcmExtrator.advance();
                    }
                }

                while (true) {
                    int outputBufferIndex = encoder.dequeueOutputBuffer(info, TIMEOUT_US);
                    if (outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
                        break;
                    } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                        MediaFormat newFormat = encoder.getOutputFormat();
                        CL.it(TAG, "audio decode newFormat = " + newFormat);
                    } else if (outputBufferIndex < 0) {
                        //ignore
                        CL.et(TAG, "unexpected result from audio decoder.dequeueOutputBuffer: " + outputBufferIndex);
                    } else {
                        if (info.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
                            encodeDone = true;
                            break;
                        }
                        ByteBuffer encodeOutputBuffer = encoder.getOutputBuffer(outputBufferIndex);
                        CL.it(TAG, "audio writeSampleData " + info.presentationTimeUs + " size:" + info.size + " flags:" + info.flags);
                        if (!detectTimeError && lastAudioFrameTimeUs != -1 && info.presentationTimeUs < lastAudioFrameTimeUs + AAC_FRAME_TIME_US) {
                            //某些情况下帧时间会出错,目前未找到原因(系统相机录得双声道视频正常,我录的单声道视频不正常)
                            CL.et(TAG, "audio 时间戳错误,lastAudioFrameTimeUs:" + lastAudioFrameTimeUs + " " +
                                    "info.presentationTimeUs:" + info.presentationTimeUs);
                            detectTimeError = true;
                        }
                        if (detectTimeError) {
                            info.presentationTimeUs = lastAudioFrameTimeUs + AAC_FRAME_TIME_US;
                            CL.et(TAG, "audio 时间戳错误,使用修正的时间戳:" + info.presentationTimeUs);
                            detectTimeError = false;
                        }
                        if (info.flags != MediaCodec.BUFFER_FLAG_CODEC_CONFIG) {
                            lastAudioFrameTimeUs = info.presentationTimeUs;
                        }
                        mediaMuxer.writeSampleData(muxerAudioIndex, encodeOutputBuffer, info);

                        encodeOutputBuffer.clear();
                        encoder.releaseOutputBuffer(outputBufferIndex, false);
                    }
                }
            }
            //重新将视频写入
            if (oriAudioIndex >= 0) {
                oriExtrator.unselectTrack(oriAudioIndex);
            }
            oriExtrator.selectTrack(oriVideoIndex);
            oriExtrator.seekTo(startTimeUs, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
            maxBufferSize = oriVideoFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
            int frameRate = oriVideoFormat.containsKey(MediaFormat.KEY_FRAME_RATE) ? oriVideoFormat.getInteger(MediaFormat.KEY_FRAME_RATE) : (int) Math.ceil(VideoUtil.getAveFrameRate(videoInput));
            buffer = ByteBuffer.allocateDirect(maxBufferSize);
            final int VIDEO_FRAME_TIME_US = (int) (1000 * 1000f / frameRate);
            long lastVideoFrameTimeUs = -1;
            detectTimeError = false;
            while (true) {
                long sampleTimeUs = oriExtrator.getSampleTime();
                if (sampleTimeUs == -1) {
                    break;
                }
                if (sampleTimeUs < startTimeUs) {
                    oriExtrator.advance();
                    continue;
                }
                if (endTimeUs != null && sampleTimeUs > endTimeUs) {
                    break;
                }
                info.presentationTimeUs = sampleTimeUs - startTimeUs;
                info.flags = oriExtrator.getSampleFlags();
                info.size = oriExtrator.readSampleData(buffer, 0);
                if (info.size < 0) {
                    break;
                }
                //写入视频
                if (!detectTimeError && lastVideoFrameTimeUs != -1 && info.presentationTimeUs < lastVideoFrameTimeUs + VIDEO_FRAME_TIME_US) {
                    //某些视频帧时间会出错
                    CL.et(TAG, "video 时间戳错误,lastVideoFrameTimeUs:" + lastVideoFrameTimeUs + " " +
                            "info.presentationTimeUs:" + info.presentationTimeUs + " VIDEO_FRAME_TIME_US:" + VIDEO_FRAME_TIME_US);
                    detectTimeError = true;
                }
                if (detectTimeError) {
                    info.presentationTimeUs = lastVideoFrameTimeUs + VIDEO_FRAME_TIME_US;
                    CL.et(TAG, "video 时间戳错误,使用修正的时间戳:" + info.presentationTimeUs);
                    detectTimeError = false;
                }
                if (info.flags != MediaCodec.BUFFER_FLAG_CODEC_CONFIG) {
                    lastVideoFrameTimeUs = info.presentationTimeUs;
                }
                CL.wt(TAG, "video writeSampleData:" + info.presentationTimeUs + " type:" + info.flags + " size:" + info.size);
                mediaMuxer.writeSampleData(muxerVideoIndex, buffer, info);
                oriExtrator.advance();
            }
        } finally {
            aacPcmFile.delete();
            videoPcmFile.delete();
            adjustedPcm.delete();
            wavFile.delete();

            try {
                pcmExtrator.release();
                oriExtrator.release();
                encoder.stop();
                encoder.release();
                mediaMuxer.release();
            } catch (Exception e) {
                CL.e(e);
            }
        }
    }

    public static Processor processor(Context context) {
        return new Processor(context);
    }

    public static class Processor {
        private Context context;
        private com.hw.videoprocessor.VideoProcessor.MediaSource input;
        private String output;
        @Nullable
        private Integer outWidth;
        @Nullable
        private Integer outHeight;
        @Nullable
        private Integer startTimeMs;
        @Nullable
        private Integer endTimeMs;
        @Nullable
        private Float speed;
        @Nullable
        private Boolean changeAudioSpeed;
        @Nullable
        private Integer bitrate;
        @Nullable
        private Integer frameRate;
        @Nullable
        private Integer iFrameInterval;
        @Nullable
        private VideoProgressListener listener;
        /**
         * 帧率超过指定帧率时是否丢帧
         */
        private boolean dropFrames = true;

        public Processor(Context context) {
            this.context = context;
        }

        public Processor input(com.hw.videoprocessor.VideoProcessor.MediaSource input) {
            this.input = input;
            return this;
        }
        public Processor input(Uri input) {
            this.input = new com.hw.videoprocessor.VideoProcessor.MediaSource(context,input);
            return this;
        }

        public Processor input(String input) {
            this.input = new com.hw.videoprocessor.VideoProcessor.MediaSource(input);
            return this;
        }

        public Processor output(String output) {
            this.output = output;
            return this;
        }

        public Processor outWidth(int outWidth) {
            this.outWidth = outWidth;
            return this;
        }

        public Processor outHeight(int outHeight) {
            this.outHeight = outHeight;
            return this;
        }

        public Processor startTimeMs(int startTimeMs) {
            this.startTimeMs = startTimeMs;
            return this;
        }

        public Processor endTimeMs(int endTimeMs) {
            this.endTimeMs = endTimeMs;
            return this;
        }

        public Processor speed(float speed) {
            this.speed = speed;
            return this;
        }

        public Processor changeAudioSpeed(boolean changeAudioSpeed) {
            this.changeAudioSpeed = changeAudioSpeed;
            return this;
        }

        public Processor bitrate(int bitrate) {
            this.bitrate = bitrate;
            return this;
        }

        public Processor frameRate(int frameRate) {
            this.frameRate = frameRate;
            return this;
        }

        public Processor iFrameInterval(int iFrameInterval) {
            this.iFrameInterval = iFrameInterval;
            return this;
        }

        /**
         * 帧率超过指定帧率时是否丢帧,默认为true
         */
        public Processor dropFrames(boolean dropFrames) {
            this.dropFrames = dropFrames;
            return this;
        }

        public Processor progressListener(VideoProgressListener listener) {
            this.listener = listener;
            return this;
        }

        public void process() throws Exception {
            processVideo(context, this);
        }
    }

}

猜你喜欢

转载自blog.csdn.net/As_thin/article/details/131536854
今日推荐