视频压缩是一个有关视频类项目必不可少的环节,选择一个合适且稳定的压缩工具更是领开发者比较头疼的一件事情,网上压缩工具比比皆是,一旦入坑,如果出问题后期出现问题,各种成本更是令人畏惧,这篇文章或许可以让你少走一些“弯路”。
首先这里的视频压缩使用的是 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 类,加一下防护,类似于 默认数据都是原有参数,尽量自己不要乱改文章来源:https://www.toymoban.com/news/detail-642931.html
int originWidth = 1600;
if (retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH) != null){
originWidth = Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
}
最后贴上加防护后的VideoProcessor 代码文章来源地址https://www.toymoban.com/news/detail-642931.html
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);
}
}
}
到了这里,关于Android进阶之视频压缩的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!