视频压缩是一个有关视频类项目必不可少的环节,选择一个合适且稳定的压缩工具更是领开发者比较头疼的一件事情,网上压缩工具比比皆是,一旦入坑,如果出问题后期出现问题,各种成本更是令人畏惧,这篇文章或许可以让你少走一些“弯路”。
首先这里的视频压缩使用的是 VideoProcessor 介意者勿扰~,并且是音视频类实战项目长期稳定之后才写的此文章,压缩比基本保持在 7:3 左右。
接下来开始实战使用,以及遇到的问题。
1.导入依赖
com.github.yellowcath:VideoProcessor:2.4.2
2.调用方法
VideoProcessor.processor(mPresenter)
.input(url)
.outWidth(1600)
.outHeight(1200)
.output(outputUrl)
.bitrate(mBitrate)
.frameRate(10)
.process()
方法介绍
.processor(mPresenter) - mPresenter 传入当前引用
.input(url) - url本地视频地址
.outWidth(1600) - 压缩后的宽度
.outHeight(1200) - 压缩后的高度
.output(outputUrl) - 压缩后的地址
.bitrate(mBitrate) - 比特率
.frameRate(10) - 帧速率
比特率会影响到压缩视频之后的效果,可以动态去设置比特率和帧速率去调整压缩效果
//默认三百万,有数据后拿数据的百分之四十
var mBitrate = 3000000
try {
//拿到视频的比特率
var media = MediaMetadataRetriever()
media.setDataSource(locationVideoUrl)
val extractMetadata =
media.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE)
Log.e(ContentValues.TAG, "当前视频的大小242412412 比特率->:${extractMetadata}")
if (extractMetadata != null && extractMetadata.isNotEmpty()) {
mBitrate = (extractMetadata.toInt() * 0.4).toInt()
}
} catch (e: Exception) {
e.printStackTrace()
}
以上就是压缩视频的使用步骤
以下是出现的问题
首先上述代码中很明显的错误就是压缩后的宽高是写死的,这样当用户传入不同形状的大小肯定会变形,所以我们可以根据原视频宽高进行压缩
基本我们会去本地拿资源会经过 onActivityResult 回调并且拿到 Intent data
可以通过
public static List<LocalMedia> obtainMultipleResult(Intent data) {
List<LocalMedia> result = new ArrayList<>();
if (data != null) {
result = (List<LocalMedia>) data.getSerializableExtra(PictureConfig.EXTRA_RESULT_SELECTION);
if (result == null) {
result = new ArrayList<>();
}
return result;
}
return result;
}
LocalMedia
public class LocalMedia implements Parcelable {
private String path;
private String compressPath;
private String cutPath;
private long duration;
private boolean isChecked;
private boolean isCut;
public int position;
private int num;
private int mimeType;
private String pictureType;
private boolean compressed;
private int width;
private int height;
public String ossUrl;//记录上传成功后的图片地址
public boolean isFail;//新增业务字段 是否是违规
public LocalMedia() {
}
public LocalMedia(String path, long duration, int mimeType, String pictureType) {
this.path = path;
this.duration = duration;
this.mimeType = mimeType;
this.pictureType = pictureType;
}
public LocalMedia(String path, long duration, int mimeType, String pictureType, int width, int height) {
this.path = path;
this.duration = duration;
this.mimeType = mimeType;
this.pictureType = pictureType;
this.width = width;
this.height = height;
}
public LocalMedia(String path, long duration,
boolean isChecked, int position, int num, int mimeType) {
this.path = path;
this.duration = duration;
this.isChecked = isChecked;
this.position = position;
this.num = num;
this.mimeType = mimeType;
}
public String getPictureType() {
if (TextUtils.isEmpty(pictureType)) {
pictureType = "image/jpeg";
}
return pictureType;
}
public void setPictureType(String pictureType) {
this.pictureType = pictureType;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getCompressPath() {
return compressPath;
}
public void setCompressPath(String compressPath) {
this.compressPath = compressPath;
}
public String getCutPath() {
return cutPath;
}
public void setCutPath(String cutPath) {
this.cutPath = cutPath;
}
public long getDuration() {
return duration;
}
public void setDuration(long duration) {
this.duration = duration;
}
public boolean isChecked() {
return isChecked;
}
public void setChecked(boolean checked) {
isChecked = checked;
}
public boolean isCut() {
return isCut;
}
public void setCut(boolean cut) {
isCut = cut;
}
public int getPosition() {
return position;
}
public void setPosition(int position) {
this.position = position;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public int getMimeType() {
return mimeType;
}
public void setMimeType(int mimeType) {
this.mimeType = mimeType;
}
public boolean isCompressed() {
return compressed;
}
public void setCompressed(boolean compressed) {
this.compressed = compressed;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.path);
dest.writeString(this.compressPath);
dest.writeString(this.cutPath);
dest.writeLong(this.duration);
dest.writeByte(this.isChecked ? (byte) 1 : (byte) 0);
dest.writeByte(this.isCut ? (byte) 1 : (byte) 0);
dest.writeInt(this.position);
dest.writeInt(this.num);
dest.writeInt(this.mimeType);
dest.writeString(this.pictureType);
dest.writeByte(this.compressed ? (byte) 1 : (byte) 0);
dest.writeInt(this.width);
dest.writeInt(this.height);
}
protected LocalMedia(Parcel in) {
this.path = in.readString();
this.compressPath = in.readString();
this.cutPath = in.readString();
this.duration = in.readLong();
this.isChecked = in.readByte() != 0;
this.isCut = in.readByte() != 0;
this.position = in.readInt();
this.num = in.readInt();
this.mimeType = in.readInt();
this.pictureType = in.readString();
this.compressed = in.readByte() != 0;
this.width = in.readInt();
this.height = in.readInt();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
LocalMedia that = (LocalMedia) o;
return path != null && path.equals(that.path);
}
@Override
public int hashCode() {
if (path != null) {
return path.hashCode();
} else {
return 0;
}
}
public static final Parcelable.Creator<LocalMedia> CREATOR = new Parcelable.Creator<LocalMedia>() {
@Override
public LocalMedia createFromParcel(Parcel source) {
return new LocalMedia(source);
}
@Override
public LocalMedia[] newArray(int size) {
return new LocalMedia[size];
}
};
}
将拿到的data 转成 LocalMedia 的集合,默认取第一个 result.get(0)
此时我们就拿到了LocalMedia 这里面有我们需要的宽、高、时间、本地路径等信息
此时我们就可以将上面代码改成
Int videoWith = 1600
Int videoHeight = 1200
if (videoMedia.width != null && videoMedia.width != 0){
videoWith = videoMedia.width
}
if (videoMedia.height != null && videoMedia.height != 0){
videoHeight = videoMedia.height
}
VideoProcessor.processor(mPresenter)
.input(url)
.outWidth(videoWith )
.outHeight(videoHeight )
.output(outputUrl)
.bitrate(mBitrate)
.frameRate(10)
.process()
以上,视频压缩之后变形的问题就解决了
我们压缩之后会出现一个新的压缩后的路径,如果不及时删除,用户手机上就会多一个压缩之后的文件,会影响用户的使用体验,当我们使用之后要及时去删除压缩后的文件(这里不贴代码了,百度一大堆,如有需要请留言~)
删除的时候,部分机型会报错,此时需要注意了!安卓现在已经不允许对 /0 文件也就是系统默认文件进行操作了,所以我们设置的压缩后的路径不要放在 /0 目录下
可以这样
//压缩视频,使用完后别忘了把压缩后的视频删除掉
val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"
var outputUrl =
mPresenter.getExternalFilesDir("").toString() + "/Kome" + SimpleDateFormat(
FILENAME_FORMAT,
Locale.CHINA
).format(System.currentTimeMillis()) + ".mp4"
这样基本都压缩步骤已经完成了,不出意外的话就要发版了,但是发上去之后就会发现部分机型会出现空指针的问题,经排查问题发生在底层源码里面 …processVideo()方法里面报错
可能最新版本已经修复此问题,如果没有可以直接重写 VideoProcessor 类,加一下防护,类似于 默认数据都是原有参数,尽量自己不要乱改
int originWidth = 1600;
if (retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH) != null){
originWidth = Integer.parseInt(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
}
最后贴上加防护后的VideoProcessor 代码
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.media.AudioFormat;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.media.MediaMetadataRetriever;
import android.media.MediaMuxer;
import android.media.MediaRecorder;
import android.net.Uri;
import android.util.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.hw.videoprocessor.util.AudioUtil.getAudioBitrate;
import com.hw.videoprocessor.AudioProcessThread;
import com.hw.videoprocessor.VideoDecodeThread;
import com.hw.videoprocessor.VideoEncodeThread;
import com.hw.videoprocessor.VideoUtil;
import com.hw.videoprocessor.util.AudioFadeUtil;
import com.hw.videoprocessor.util.AudioUtil;
import com.hw.videoprocessor.util.CL;
import com.hw.videoprocessor.util.PcmToWavUtil;
import com.hw.videoprocessor.util.VideoMultiStepProgress;
import com.hw.videoprocessor.util.VideoProgressAve;
import com.hw.videoprocessor.util.VideoProgressListener;
@TargetApi(21)
public class VideoProcessor {
final static String TAG = "VideoProcessor";
final static String OUTPUT_MIME_TYPE = "video/avc";
public static int DEFAULT_FRAME_RATE = 20;
/**
* 只有关键帧距为0的才能方便做逆序
*/
public final static int DEFAULT_I_FRAME_INTERVAL = 1;
public final static int DEFAULT_AAC_BITRATE = 192 * 1000;
/**
* 控制音频合成时,如果输入的音频文件长度不够,是否重复填充
*/
public static boolean AUDIO_MIX_REPEAT = true;
final static int TIMEOUT_USEC = 2500;
public static void scaleVideo(Context context, Uri input, String output,
int outWidth, int outHeight) throws Exception {
processor(context)
.input(input)
.output(output)
.outWidth(outWidth)
.outHeight(outHeight)
.process();
}
public static void cutVideo(Context context, Uri input, String output, int startTimeMs, int endTimeMs) throws Exception {
processor(context)
.input(input)
.output(output)
.startTimeMs(startTimeMs)
.endTimeMs(endTimeMs)
.process();
}
public static void changeVideoSpeed(Context context, Uri input, String output, float speed) throws Exception {
processor(context)
.input(input)
.output(output)
.speed(speed)
.process();
}
/**
* 对视频先检查,如果不是全关键帧,先处理成所有帧都是关键帧,再逆序
*/
public static void reverseVideo(Context context, com.hw.videoprocessor.VideoProcessor.MediaSource input, String output, boolean reverseAudio, @Nullable VideoProgressListener listener) throws Exception {
File tempFile = new File(context.getCacheDir(), System.currentTimeMillis() + ".temp");
File temp2File = new File(context.getCacheDir(), System.currentTimeMillis() + ".temp2");
try {
MediaExtractor extractor = new MediaExtractor();
input.setDataSource(extractor);
int trackIndex = VideoUtil.selectTrack(extractor, false);
extractor.selectTrack(trackIndex);
int keyFrameCount = 0;
int frameCount = 0;

文章介绍了在Android开发中使用VideoProcessor库进行视频压缩时可能出现的宽高比变形问题,提供了解决方案,包括根据原视频宽高动态设置压缩参数,以及处理压缩后文件的清理。此外,还讨论了如何避免一些异常情况,如空指针异常,确保压缩过程的稳定性。
最低0.47元/天 解锁文章
1186





