Android进阶之视频压缩,Android视频压缩进阶指南

马肤

温馨提示:这篇文章已超过425天没有更新,请注意相关的内容是否还可用!

摘要:,,本内容主要介绍了Android开发中的视频压缩技术。详细介绍了如何进行视频压缩,包括压缩工具的选择、压缩参数的设置以及压缩过程中的注意事项等。通过学习和实践,开发者可以掌握视频压缩的技巧,优化视频文件大小,节省存储空间,提高应用性能。这对于需要处理大量视频数据的Android应用来说,具有重要的实用价值。

视频压缩是一个有关视频类项目必不可少的环节,选择一个合适且稳定的压缩工具更是领开发者比较头疼的一件事情,网上压缩工具比比皆是,一旦入坑,如果出问题后期出现问题,各种成本更是令人畏惧,这篇文章或许可以让你少走一些“弯路”。

Android进阶之视频压缩,Android视频压缩进阶指南 第1张
(图片来源网络,侵删)

首先这里的视频压缩使用的是 VideoProcessor 介意者勿扰~,并且是音视频类实战项目长期稳定之后才写的此文章,压缩比基本保持在 7:3 左右。

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

Android进阶之视频压缩,Android视频压缩进阶指南 第2张
(图片来源网络,侵删)

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 obtainMultipleResult(Intent data) {
        List result = new ArrayList();
        if (data != null) {
            result = (List) 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 CREATOR = new Parcelable.Creator() {
        @Override
        public LocalMedia createFromParcel(Parcel source) {
            return new LocalMedia(source);
        }
        @Override
        public LocalMedia[] newArray(int size) {
            return new LocalMedia[size];
        }
    };
}

将拿到的data 转成 LocalMedia 的集合,默认取第一个 result.get(0)

此时我们就拿到了LocalMedia 这里面有我们需要的宽、高、时间、本地路径等信息

此时我们就可以将上面代码改成

Int  videoWith = 1600
Int  videoHeight = 1200
if (videoMedia.width != null && videoMedia.width != 0){
          videoWith = videoMedia.width
   }
if (videoMedia.height != null && videoMedia.height != 0){
          videoHeight = videoMedia.height
   }
VideoProcessor.processor(mPresenter)
              .input(url)
              .outWidth(videoWith )
              .outHeight(videoHeight )
              .output(outputUrl)
              .bitrate(mBitrate)
              .frameRate(10)
              .process()

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

我们压缩之后会出现一个新的压缩后的路径,如果不及时删除,用户手机上就会多一个压缩之后的文件,会影响用户的使用体验,当我们使用之后要及时去删除压缩后的文件(这里不贴代码了,百度一大堆,如有需要请留言~)

删除的时候,部分机型会报错,此时需要注意了!安卓现在已经不允许对 /0 文件也就是系统默认文件进行操作了,所以我们设置的压缩后的路径不要放在 /0 目录下

可以这样

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

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

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

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

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

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.media.AudioFormat;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.media.MediaMetadataRetriever;
import android.media.MediaMuxer;
import android.media.MediaRecorder;
import android.net.Uri;
import android.util.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.hw.videoprocessor.util.AudioUtil.getAudioBitrate;
import com.hw.videoprocessor.AudioProcessThread;
import com.hw.videoprocessor.VideoDecodeThread;
import com.hw.videoprocessor.VideoEncodeThread;
import com.hw.videoprocessor.VideoUtil;
import com.hw.videoprocessor.util.AudioFadeUtil;
import com.hw.videoprocessor.util.AudioUtil;
import com.hw.videoprocessor.util.CL;
import com.hw.videoprocessor.util.PcmToWavUtil;
import com.hw.videoprocessor.util.VideoMultiStepProgress;
import com.hw.videoprocessor.util.VideoProgressAve;
import com.hw.videoprocessor.util.VideoProgressListener;
@TargetApi(21)
public class VideoProcessor {
    final static String TAG = "VideoProcessor";
    final static String OUTPUT_MIME_TYPE = "video/avc";
    public static int DEFAULT_FRAME_RATE = 20;
    /**
     * 只有关键帧距为0的才能方便做逆序
     */
    public final static int DEFAULT_I_FRAME_INTERVAL = 1;
    public final static int DEFAULT_AAC_BITRATE = 192 * 1000;
    /**
     * 控制音频合成时,如果输入的音频文件长度不够,是否重复填充
     */
    public static boolean AUDIO_MIX_REPEAT = true;
    final static int TIMEOUT_USEC = 2500;
    public static void scaleVideo(Context context, Uri input, String output,
                                  int outWidth, int outHeight) throws Exception {
        processor(context)
                .input(input)
                .output(output)
                .outWidth(outWidth)
                .outHeight(outHeight)
                .process();
    }
    public static void cutVideo(Context context, Uri input, String output, int startTimeMs, int endTimeMs) throws Exception {
        processor(context)
                .input(input)
                .output(output)
                .startTimeMs(startTimeMs)
                .endTimeMs(endTimeMs)
                .process();
    }
    public static void changeVideoSpeed(Context context, Uri input, String output, float speed) throws Exception {
        processor(context)
                .input(input)
                .output(output)
                .speed(speed)
                .process();
    }
    /**
     * 对视频先检查,如果不是全关键帧,先处理成所有帧都是关键帧,再逆序
     */
    public static void reverseVideo(Context context, com.hw.videoprocessor.VideoProcessor.MediaSource input, String output, boolean reverseAudio, @Nullable VideoProgressListener listener) throws Exception {
        File tempFile = new File(context.getCacheDir(), System.currentTimeMillis() + ".temp");
        File temp2File = new File(context.getCacheDir(), System.currentTimeMillis() + ".temp2");
        try {
            MediaExtractor extractor = new MediaExtractor();
            input.setDataSource(extractor);
            int trackIndex = VideoUtil.selectTrack(extractor, false);
            extractor.selectTrack(trackIndex);
            int keyFrameCount = 0;
            int frameCount = 0;
            List 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) {
            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  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  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  1 ? 1 : videoProgress;
                        videoProgress *= 0.7f;
                        listener.onProgress(videoProgress);
                    }
                    long seekTime = sampleTime - MIN_FRAME_INTERVAL;
                    if (seekTime = 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  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  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 getFrameTimeStampsList(MediaExtractor extractor){
        List frameTimeStamps = new ArrayList();
        while (true) {
            long sampleTime = extractor.getSampleTime();
            if (sampleTime  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) {
                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) {
            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 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) {
                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  endTimeUs) {
                    break;
                }
                info.presentationTimeUs = sampleTimeUs - startTimeUs;
                info.flags = oriExtrator.getSampleFlags();
                info.size = oriExtrator.readSampleData(buffer, 0);
                if (info.size 
                
                
                

0
收藏0
文章版权声明:除非注明,否则均为VPS857原创文章,转载或复制请以超链接形式并注明出处。

相关阅读

  • 【研发日记】Matlab/Simulink自动生成代码(二)——五种选择结构实现方法,Matlab/Simulink自动生成代码的五种选择结构实现方法(二),Matlab/Simulink自动生成代码的五种选择结构实现方法详解(二)
  • 超级好用的C++实用库之跨平台实用方法,跨平台实用方法的C++实用库超好用指南,C++跨平台实用库使用指南,超好用实用方法集合,C++跨平台实用库超好用指南,方法与技巧集合
  • 【动态规划】斐波那契数列模型(C++),斐波那契数列模型(C++实现与动态规划解析),斐波那契数列模型解析与C++实现(动态规划)
  • 【C++】,string类底层的模拟实现,C++中string类的模拟底层实现探究
  • uniapp 小程序实现微信授权登录(前端和后端),Uniapp小程序实现微信授权登录全流程(前端后端全攻略),Uniapp小程序微信授权登录全流程攻略,前端后端全指南
  • Vue脚手架的安装(保姆级教程),Vue脚手架保姆级安装教程,Vue脚手架保姆级安装指南,Vue脚手架保姆级安装指南,从零开始教你如何安装Vue脚手架
  • 如何在树莓派 Raspberry Pi中本地部署一个web站点并实现无公网IP远程访问,树莓派上本地部署Web站点及无公网IP远程访问指南,树莓派部署Web站点及无公网IP远程访问指南,本地部署与远程访问实践,树莓派部署Web站点及无公网IP远程访问实践指南,树莓派部署Web站点及无公网IP远程访问实践指南,本地部署与远程访问详解,树莓派部署Web站点及无公网IP远程访问实践详解,本地部署与远程访问指南,树莓派部署Web站点及无公网IP远程访问实践详解,本地部署与远程访问指南。
  • vue2技术栈实现AI问答机器人功能(流式与非流式两种接口方法),Vue2技术栈实现AI问答机器人功能,流式与非流式接口方法探究,Vue2技术栈实现AI问答机器人功能,流式与非流式接口方法详解
  • 发表评论

    快捷回复:表情:
    评论列表 (暂无评论,0人围观)

    还没有评论,来说两句吧...

    目录[+]

    取消
    微信二维码
    微信二维码
    支付宝二维码