视频压缩是一个有关视频类项目必不可少的环节,选择一个合适且稳定的压缩工具更是领开发者比较头疼的一件事情,网上压缩工具比比皆是,一旦入坑,如果出问题后期出现问题,各种成本更是令人畏惧,这篇文章或许可以让你少走一些“弯路”。
首先这里的视频压缩使用的是 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 = 3000000try {//拿到视频的比特率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;}@Overridepublic int describeContents() {return 0;}@Overridepublic 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();}@Overridepublic 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);}@Overridepublic int hashCode() {if (path != null) {return path.hashCode();} else {return 0;}}public static final Parcelable.Creator<LocalMedia> CREATOR = new Parcelable.Creator<LocalMedia>() {@Overridepublic LocalMedia createFromParcel(Parcel source) {return new LocalMedia(source);}@Overridepublic 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) {//ignoreCL.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);//音频转化为PCMnew Thread(new Runnable() {@Overridepublic 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() {@Overridepublic 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();//检查两段音频格式是否一致,不一致则统一转换为单声道+44100Pair<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转WAVnew 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转WAVnew 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) {//ignoreCL.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;@Nullableprivate Integer outWidth;@Nullableprivate Integer outHeight;@Nullableprivate Integer startTimeMs;@Nullableprivate Integer endTimeMs;@Nullableprivate Float speed;@Nullableprivate Boolean changeAudioSpeed;@Nullableprivate Integer bitrate;@Nullableprivate Integer frameRate;@Nullableprivate Integer iFrameInterval;@Nullableprivate 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);}}}

Android进阶之视频压缩相关推荐

  1. Android进阶笔记:Messenger源码详解

    Messenger可以理解为一个是用于发送消息的一个类用法也很多,这里主要分析一下再跨进程的情况下Messenger的实现流程与源码分析.相信结合前面两篇关于aidl解析文章能够更好的对aidl有一个 ...

  2. 我的Android进阶之旅------Java字符串格式化方法String.format()格式化float型时小数点变成逗号问题...

    今天接到一个波兰的客户说有个APP在英文状态下一切运行正常,但是当系统语言切换到波兰语言的时候,程序奔溃了.好吧,又是我来维护. 好吧,先把系统语言切换到波兰语,切换到波兰语的方法查看文章 我的And ...

  3. Android进阶笔记:AIDL内部实现详解 (二)

    接着上一篇分析的aidl的流程解析.知道了aidl主要就是利用Ibinder来实现跨进程通信的.既然是通过对Binder各种方法的封装,那也可以不使用aidl自己通过Binder来实现跨进程通讯.那么 ...

  4. 我的Android进阶之旅------gt;Android字符串资源中的单引號问题error: Apostrophe not preceded by 的解决的方法...

    刚刚在string字符串资源文件里,写了一个单引號.报错了,错误代码例如以下 error: Apostrophe not preceded by \ (in OuyangPeng's blog ) 资 ...

  5. android java服务,Android进阶学习必会:Java Binder中的系统服务

    前言 这个知识点是Android进阶学习必须掌握的知识点之一,也是高阶Android架构师经常问到的点.在这里分想给大家,希望对大家的工作和学习有所帮助.喜欢本文的记得点赞关注哦~ 在前面的Andro ...

  6. Android进阶知识:绘制流程(上)

    1.前言 之前写过一篇Android进阶知识:事件分发与滑动冲突,主要研究的是关于Android中View事件分发与响应的流程.关于View除了事件传递流程还有一个很重要的就是View的绘制流程.一个 ...

  7. Android进阶篇之引导页系列之ViewPager实现Animation动画引导页

    Android进阶篇之引导页系列之ViewPager实现Animation动画引导页 转载于:https://www.cnblogs.com/zhujiabin/p/5795789.html

  8. Android 进阶 教你打造 Android 中的 IOC 框架 【ViewInject】 (下)

    上一篇博客我们已经带大家简单的吹了一下IoC,实现了Activity中View的布局以及控件的注入,如果你不了解,请参考:Android 进阶 教你打造 Android 中的 IOC 框架 [View ...

  9. 我的Android进阶之旅------gt;解决Jackson等第三方转换Json的开发包在开启混淆后转换的实体类数据都是null的bug...

    1.错误描述 今天测试人员提了一个bug,说使用我们的app出现了闪退的bug,后来通过debug断点调试,发现我们的app转换服务器发送过来的json数据后,都是为null.而之前已经提测快一个月的 ...

最新文章

  1. java二级下拉菜单_航菜单栏中的二级下拉菜单
  2. 【java】java自带的java.util.logging.Logger日志功能
  3. Android之HTTP预备知识
  4. Oracle优化07-分析及动态采样-动态采样
  5. python直方图均衡化代码_基于matlab的直方图均衡化代码
  6. Java黑皮书课后题第8章:8.28(严格相同的数组)如果两个二维数组m1和m2相应的元素相等的话,则认为它们是严格地相同的。编写一个方法,如果m1和m2是严格相同的话,返回true
  7. Mac 开机没声音了?只需 2 招关闭/恢复
  8. 【181220】VC++ 简易的人工智能模型源程序源代码
  9. 计算机视觉论文-2021-04-06
  10. python保存excel文件列宽自适应解决方案
  11. 迅雷和flashgot 出现AddRef问题
  12. 习题4-1 求奇数和 (15分) 本题要求计算给定的一系列正整数中奇数的和
  13. 解决WPS每点击一下保存,就会出现tmp文件
  14. 我跨过山和大海,穿过人山人海,只为寻找到你
  15. php7 fileinfo,PHP7.3开启fileinfo扩展
  16. CSS Grid 布局
  17. 嵌入式开发:硬件和软件越来越接近
  18. python马尔可夫链_[译] 用 Python 实现马尔可夫链的初级教程
  19. android 视频转音频,将mp4文件转换为mp3文件的方法
  20. Spartan-6的时钟管理 DCM与 PLL详细介绍

热门文章

  1. ES6 Promise的理解
  2. 关于数码照片的打印和分辨率的有关问题的探讨《转》
  3. 精彩回顾:终于明白阿里百度为什么拿WaitNotify通知机制考察求职者了
  4. vue中赋值操作深入
  5. python之retry函数
  6. 机器学习 (十四)轻松理解模型评价指标
  7. MFC中的DDX和DDV
  8. 05 爬取周杰伦所有歌单
  9. android人脸检测开发——百度大脑离线识别SDK
  10. jupyter notebook 添加核 (jupyter:ModuleNotFoundError: No module named ‘torch‘) 亲测可用