NekoX/TMessagesProj/src/main/java/org/telegram/messenger/video/MediaCodecVideoConvertor.java

1047 lines
60 KiB
Java

package org.telegram.messenger.video;
import android.annotation.TargetApi;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaCodecList;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.os.Build;
import com.google.android.exoplayer2.util.Log;
import org.telegram.messenger.AndroidUtilities;
import org.telegram.messenger.BuildVars;
import org.telegram.messenger.FileLog;
import org.telegram.messenger.MediaController;
import org.telegram.messenger.Utilities;
import org.telegram.messenger.VideoEditedInfo;
import java.io.File;
import java.nio.ByteBuffer;
import java.util.ArrayList;
public class MediaCodecVideoConvertor {
private MP4Builder mediaMuxer;
private MediaExtractor extractor;
private long endPresentationTime;
private MediaController.VideoConvertorListener callback;
private final static int PROCESSOR_TYPE_OTHER = 0;
private final static int PROCESSOR_TYPE_QCOM = 1;
private final static int PROCESSOR_TYPE_INTEL = 2;
private final static int PROCESSOR_TYPE_MTK = 3;
private final static int PROCESSOR_TYPE_SEC = 4;
private final static int PROCESSOR_TYPE_TI = 5;
private static final int MEDIACODEC_TIMEOUT_DEFAULT = 2500;
private static final int MEDIACODEC_TIMEOUT_INCREASED = 22000;
public boolean convertVideo(String videoPath, File cacheFile,
int rotationValue, boolean isSecret,
int originalWidth, int originalHeight,
int resultWidth, int resultHeight,
int framerate, int bitrate, int originalBitrate,
long startTime, long endTime, long avatarStartTime,
boolean needCompress, long duration,
MediaController.SavedFilterState savedFilterState,
String paintPath,
ArrayList<VideoEditedInfo.MediaEntity> mediaEntities,
boolean isPhoto,
MediaController.CropState cropState,
boolean isRound,
MediaController.VideoConvertorListener callback) {
this.callback = callback;
return convertVideoInternal(videoPath, cacheFile, rotationValue, isSecret, originalWidth, originalHeight,
resultWidth, resultHeight, framerate, bitrate, originalBitrate, startTime, endTime, avatarStartTime, duration, needCompress, false, savedFilterState, paintPath, mediaEntities, isPhoto, cropState, isRound);
}
public long getLastFrameTimestamp() {
return endPresentationTime;
}
@TargetApi(18)
private boolean convertVideoInternal(String videoPath, File cacheFile,
int rotationValue, boolean isSecret,
int originalWidth, int originalHeight,
int resultWidth, int resultHeight,
int framerate, int bitrate, int originalBitrate,
long startTime, long endTime, long avatarStartTime,
long duration,
boolean needCompress, boolean increaseTimeout,
MediaController.SavedFilterState savedFilterState,
String paintPath,
ArrayList<VideoEditedInfo.MediaEntity> mediaEntities,
boolean isPhoto,
MediaController.CropState cropState,
boolean isRound) {
long time = System.currentTimeMillis();
boolean error = false;
boolean repeatWithIncreasedTimeout = false;
int videoTrackIndex = -5;
try {
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
Mp4Movie movie = new Mp4Movie();
movie.setCacheFile(cacheFile);
movie.setRotation(0);
movie.setSize(resultWidth, resultHeight);
mediaMuxer = new MP4Builder().createMovie(movie, isSecret);
long currentPts = 0;
float durationS = duration / 1000f;
MediaCodec encoder = null;
InputSurface inputSurface = null;
OutputSurface outputSurface = null;
int prependHeaderSize = 0;
endPresentationTime = duration * 1000;
checkConversionCanceled();
if (isPhoto) {
try {
boolean outputDone = false;
boolean decoderDone = false;
int framesCount = 0;
if (avatarStartTime >= 0) {
if (durationS <= 2000) {
bitrate = 2600000;
} else if (durationS <= 5000) {
bitrate = 2200000;
} else {
bitrate = 1560000;
}
} else if (bitrate <= 0) {
bitrate = 921600;
}
if (resultWidth % 16 != 0) {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("changing width from " + resultWidth + " to " + Math.round(resultWidth / 16.0f) * 16);
}
resultWidth = Math.round(resultWidth / 16.0f) * 16;
}
if (resultHeight % 16 != 0) {
if (BuildVars.LOGS_ENABLED) {
FileLog.d("changing height from " + resultHeight + " to " + Math.round(resultHeight / 16.0f) * 16);
}
resultHeight = Math.round(resultHeight / 16.0f) * 16;
}
if (BuildVars.LOGS_ENABLED) {
FileLog.d("create photo encoder " + resultWidth + " " + resultHeight + " duration = " + duration);
}
MediaFormat outputFormat = MediaFormat.createVideoFormat(MediaController.VIDEO_MIME_TYPE, resultWidth, resultHeight);
outputFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
outputFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
outputFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
outputFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
encoder = MediaCodec.createEncoderByType(MediaController.VIDEO_MIME_TYPE);
encoder.configure(outputFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
inputSurface = new InputSurface(encoder.createInputSurface());
inputSurface.makeCurrent();
encoder.start();
outputSurface = new OutputSurface(savedFilterState, videoPath, paintPath, mediaEntities, null, resultWidth, resultHeight, originalWidth, originalHeight, rotationValue, framerate, true);
ByteBuffer[] encoderOutputBuffers = null;
ByteBuffer[] encoderInputBuffers = null;
if (Build.VERSION.SDK_INT < 21) {
encoderOutputBuffers = encoder.getOutputBuffers();
}
boolean firstEncode = true;
checkConversionCanceled();
while (!outputDone) {
checkConversionCanceled();
boolean decoderOutputAvailable = !decoderDone;
boolean encoderOutputAvailable = true;
while (decoderOutputAvailable || encoderOutputAvailable) {
checkConversionCanceled();
int encoderStatus = encoder.dequeueOutputBuffer(info, increaseTimeout ? MEDIACODEC_TIMEOUT_INCREASED : MEDIACODEC_TIMEOUT_DEFAULT);
if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
encoderOutputAvailable = false;
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
if (Build.VERSION.SDK_INT < 21) {
encoderOutputBuffers = encoder.getOutputBuffers();
}
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
MediaFormat newFormat = encoder.getOutputFormat();
if (BuildVars.LOGS_ENABLED) {
FileLog.d("photo encoder new format " + newFormat);
}
if (videoTrackIndex == -5 && newFormat != null) {
videoTrackIndex = mediaMuxer.addTrack(newFormat, false);
if (newFormat.containsKey(MediaFormat.KEY_PREPEND_HEADER_TO_SYNC_FRAMES) && newFormat.getInteger(MediaFormat.KEY_PREPEND_HEADER_TO_SYNC_FRAMES) == 1) {
ByteBuffer spsBuff = newFormat.getByteBuffer("csd-0");
ByteBuffer ppsBuff = newFormat.getByteBuffer("csd-1");
prependHeaderSize = spsBuff.limit() + ppsBuff.limit();
}
}
} else if (encoderStatus < 0) {
throw new RuntimeException("unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus);
} else {
ByteBuffer encodedData;
if (Build.VERSION.SDK_INT < 21) {
encodedData = encoderOutputBuffers[encoderStatus];
} else {
encodedData = encoder.getOutputBuffer(encoderStatus);
}
if (encodedData == null) {
throw new RuntimeException("encoderOutputBuffer " + encoderStatus + " was null");
}
if (info.size > 1) {
if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
if (prependHeaderSize != 0 && (info.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) {
info.offset += prependHeaderSize;
info.size -= prependHeaderSize;
}
if (firstEncode && (info.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) {
if (info.size > 100) {
encodedData.position(info.offset);
byte[] temp = new byte[100];
encodedData.get(temp);
int nalCount = 0;
for (int a = 0; a < temp.length - 4; a++) {
if (temp[a] == 0 && temp[a + 1] == 0 && temp[a + 2] == 0 && temp[a + 3] == 1) {
nalCount++;
if (nalCount > 1) {
info.offset += a;
info.size -= a;
break;
}
}
}
}
firstEncode = false;
}
long availableSize = mediaMuxer.writeSampleData(videoTrackIndex, encodedData, info, true);
if (availableSize != 0) {
if (callback != null) {
callback.didWriteData(availableSize, (currentPts / 1000f) / durationS);
}
}
} else if (videoTrackIndex == -5) {
byte[] csd = new byte[info.size];
encodedData.limit(info.offset + info.size);
encodedData.position(info.offset);
encodedData.get(csd);
ByteBuffer sps = null;
ByteBuffer pps = null;
for (int a = info.size - 1; a >= 0; a--) {
if (a > 3) {
if (csd[a] == 1 && csd[a - 1] == 0 && csd[a - 2] == 0 && csd[a - 3] == 0) {
sps = ByteBuffer.allocate(a - 3);
pps = ByteBuffer.allocate(info.size - (a - 3));
sps.put(csd, 0, a - 3).position(0);
pps.put(csd, a - 3, info.size - (a - 3)).position(0);
break;
}
} else {
break;
}
}
MediaFormat newFormat = MediaFormat.createVideoFormat(MediaController.VIDEO_MIME_TYPE, resultWidth, resultHeight);
if (sps != null && pps != null) {
newFormat.setByteBuffer("csd-0", sps);
newFormat.setByteBuffer("csd-1", pps);
}
videoTrackIndex = mediaMuxer.addTrack(newFormat, false);
}
}
outputDone = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
encoder.releaseOutputBuffer(encoderStatus, false);
}
if (encoderStatus != MediaCodec.INFO_TRY_AGAIN_LATER) {
continue;
}
if (!decoderDone) {
outputSurface.drawImage();
long presentationTime = (long) (framesCount / 30.0f * 1000L * 1000L * 1000L);
inputSurface.setPresentationTime(presentationTime);
inputSurface.swapBuffers();
framesCount++;
if (framesCount >= duration / 1000.0f * 30) {
decoderDone = true;
decoderOutputAvailable = false;
encoder.signalEndOfInputStream();
}
}
}
}
} catch (Exception e) {
// in some case encoder.dequeueOutputBuffer return IllegalStateException
// stable reproduced on xiaomi
// fix it by increasing timeout
if (e instanceof IllegalStateException && !increaseTimeout) {
repeatWithIncreasedTimeout = true;
}
FileLog.e("bitrate: " + bitrate + " framerate: " + framerate + " size: " + resultHeight + "x" + resultWidth);
FileLog.e(e);
error = true;
}
if (outputSurface != null) {
outputSurface.release();
}
if (inputSurface != null) {
inputSurface.release();
}
if (encoder != null) {
encoder.stop();
encoder.release();
}
checkConversionCanceled();
} else {
extractor = new MediaExtractor();
extractor.setDataSource(videoPath);
int videoIndex = MediaController.findTrack(extractor, false);
int audioIndex = bitrate != -1 ? MediaController.findTrack(extractor, true) : -1;
boolean needConvertVideo = false;
if (videoIndex >= 0 && !extractor.getTrackFormat(videoIndex).getString(MediaFormat.KEY_MIME).equals(MediaController.VIDEO_MIME_TYPE)) {
needConvertVideo = true;
}
if (needCompress || needConvertVideo) {
AudioRecoder audioRecoder = null;
ByteBuffer audioBuffer = null;
boolean copyAudioBuffer = true;
long lastFramePts = -1;
if (videoIndex >= 0) {
MediaCodec decoder = null;
try {
long videoTime = -1;
boolean outputDone = false;
boolean inputDone = false;
boolean decoderDone = false;
int swapUV = 0;
int audioTrackIndex = -5;
long additionalPresentationTime = 0;
long minPresentationTime = Integer.MIN_VALUE;
long frameDelta = 1000 / framerate * 1000;
long frameDeltaFroSkipFrames;
if (framerate < 30) {
frameDeltaFroSkipFrames = 1000 / (framerate + 5) * 1000;
} else {
frameDeltaFroSkipFrames = 1000 / (framerate + 1) * 1000;
}
extractor.selectTrack(videoIndex);
MediaFormat videoFormat = extractor.getTrackFormat(videoIndex);
String encoderName = null;
if (avatarStartTime >= 0) {
if (durationS <= 2000) {
bitrate = 2600000;
} else if (durationS <= 5000) {
bitrate = 2200000;
} else {
bitrate = 1560000;
}
avatarStartTime = 0;
//this encoder work with bitrate better, prevent case when result video max 2MB
if (originalBitrate >= 15_000_000) {
encoderName = "OMX.google.h264.encoder";
}
} else if (bitrate <= 0) {
bitrate = 921600;
}
if (originalBitrate > 0) {
bitrate = Math.min(originalBitrate, bitrate);
}
long trueStartTime;// = startTime < 0 ? 0 : startTime;
if (avatarStartTime >= 0/* && trueStartTime == avatarStartTime*/) {
avatarStartTime = -1;
}
if (avatarStartTime >= 0) {
extractor.seekTo(avatarStartTime, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
} else if (startTime > 0) {
extractor.seekTo(startTime, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
} else {
extractor.seekTo(0, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
}
int w;
int h;
if (cropState != null) {
if (rotationValue == 90 || rotationValue == 270) {
w = cropState.transformHeight;
h = cropState.transformWidth;
} else {
w = cropState.transformWidth;
h = cropState.transformHeight;
}
} else {
w = resultWidth;
h = resultHeight;
}
if (BuildVars.LOGS_ENABLED) {
FileLog.d("create encoder with w = " + w + " h = " + h + " bitrate = " + bitrate);
}
MediaFormat outputFormat = MediaFormat.createVideoFormat(MediaController.VIDEO_MIME_TYPE, w, h);
outputFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
outputFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
outputFormat.setInteger(MediaFormat.KEY_FRAME_RATE, framerate);
outputFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2);
if (Build.VERSION.SDK_INT < 23 && Math.min(h, w) <= 480) {
if (bitrate > 921600) {
bitrate = 921600;
}
outputFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
}
if (encoderName != null) {
try {
encoder = MediaCodec.createByCodecName(encoderName);
} catch (Exception e) {
}
}
if (encoder == null) {
encoder = MediaCodec.createEncoderByType(MediaController.VIDEO_MIME_TYPE);
}
encoder.configure(outputFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
inputSurface = new InputSurface(encoder.createInputSurface());
inputSurface.makeCurrent();
encoder.start();
decoder = MediaCodec.createDecoderByType(videoFormat.getString(MediaFormat.KEY_MIME));
outputSurface = new OutputSurface(savedFilterState, null, paintPath, mediaEntities, cropState, resultWidth, resultHeight, originalWidth, originalHeight, rotationValue, framerate, false);
if (!isRound && Math.max(resultHeight, resultHeight) / (float) Math.max(originalHeight, originalWidth) < 0.9f) {
outputSurface.changeFragmentShader(createFragmentShader(originalWidth, originalHeight, resultWidth, resultHeight, true), createFragmentShader(originalWidth, originalHeight, resultWidth, resultHeight, false));
}
decoder.configure(videoFormat, outputSurface.getSurface(), null, 0);
decoder.start();
ByteBuffer[] decoderInputBuffers = null;
ByteBuffer[] encoderOutputBuffers = null;
ByteBuffer[] encoderInputBuffers = null;
if (Build.VERSION.SDK_INT < 21) {
decoderInputBuffers = decoder.getInputBuffers();
encoderOutputBuffers = encoder.getOutputBuffers();
}
int maxBufferSize = 0;
if (audioIndex >= 0) {
MediaFormat audioFormat = extractor.getTrackFormat(audioIndex);
copyAudioBuffer = audioFormat.getString(MediaFormat.KEY_MIME).equals(MediaController.AUIDO_MIME_TYPE) || audioFormat.getString(MediaFormat.KEY_MIME).equals("audio/mpeg");
if (audioFormat.getString(MediaFormat.KEY_MIME).equals("audio/unknown")) {
audioIndex = -1;
}
if (audioIndex >= 0) {
if (copyAudioBuffer) {
audioTrackIndex = mediaMuxer.addTrack(audioFormat, true);
extractor.selectTrack(audioIndex);
try {
maxBufferSize = audioFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
} catch (Exception e) {
FileLog.e(e); //s20 ultra exception
}
if (maxBufferSize <= 0) {
maxBufferSize = 64 * 1024;
}
audioBuffer = ByteBuffer.allocateDirect(maxBufferSize);
if (startTime > 0) {
extractor.seekTo(startTime, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
} else {
extractor.seekTo(0, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
}
} else {
MediaExtractor audioExtractor = new MediaExtractor();
audioExtractor.setDataSource(videoPath);
audioExtractor.selectTrack(audioIndex);
if (startTime > 0) {
audioExtractor.seekTo(startTime, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
} else {
audioExtractor.seekTo(0, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
}
audioRecoder = new AudioRecoder(audioFormat, audioExtractor, audioIndex);
audioRecoder.startTime = startTime;
audioRecoder.endTime = endTime;
audioTrackIndex = mediaMuxer.addTrack(audioRecoder.format, true);
}
}
}
boolean audioEncoderDone = audioIndex < 0;
boolean firstEncode = true;
checkConversionCanceled();
while (!outputDone || (!copyAudioBuffer && !audioEncoderDone)) {
checkConversionCanceled();
if (!copyAudioBuffer && audioRecoder != null) {
audioEncoderDone = audioRecoder.step(mediaMuxer, audioTrackIndex);
}
if (!inputDone) {
boolean eof = false;
int index = extractor.getSampleTrackIndex();
if (index == videoIndex) {
int inputBufIndex = decoder.dequeueInputBuffer(MEDIACODEC_TIMEOUT_DEFAULT);
if (inputBufIndex >= 0) {
ByteBuffer inputBuf;
if (Build.VERSION.SDK_INT < 21) {
inputBuf = decoderInputBuffers[inputBufIndex];
} else {
inputBuf = decoder.getInputBuffer(inputBufIndex);
}
int chunkSize = extractor.readSampleData(inputBuf, 0);
if (chunkSize < 0) {
decoder.queueInputBuffer(inputBufIndex, 0, 0, 0L, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
inputDone = true;
} else {
decoder.queueInputBuffer(inputBufIndex, 0, chunkSize, extractor.getSampleTime(), 0);
extractor.advance();
}
}
} else if (copyAudioBuffer && audioIndex != -1 && index == audioIndex) {
if (Build.VERSION.SDK_INT >= 28) {
long size = extractor.getSampleSize();
if (size > maxBufferSize) {
maxBufferSize = (int) (size + 1024);
audioBuffer = ByteBuffer.allocateDirect(maxBufferSize);
}
}
info.size = extractor.readSampleData(audioBuffer, 0);
if (Build.VERSION.SDK_INT < 21) {
audioBuffer.position(0);
audioBuffer.limit(info.size);
}
if (info.size >= 0) {
info.presentationTimeUs = extractor.getSampleTime();
extractor.advance();
} else {
info.size = 0;
inputDone = true;
}
if (info.size > 0 && (endTime < 0 || info.presentationTimeUs < endTime)) {
info.offset = 0;
info.flags = extractor.getSampleFlags();
long availableSize = mediaMuxer.writeSampleData(audioTrackIndex, audioBuffer, info, false);
if (availableSize != 0) {
if (callback != null) {
if (info.presentationTimeUs - startTime > currentPts) {
currentPts = info.presentationTimeUs - startTime;
}
callback.didWriteData(availableSize, (currentPts / 1000f) / durationS);
}
}
}
} else if (index == -1) {
eof = true;
}
if (eof) {
int inputBufIndex = decoder.dequeueInputBuffer(MEDIACODEC_TIMEOUT_DEFAULT);
if (inputBufIndex >= 0) {
decoder.queueInputBuffer(inputBufIndex, 0, 0, 0L, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
inputDone = true;
}
}
}
boolean decoderOutputAvailable = !decoderDone;
boolean encoderOutputAvailable = true;
while (decoderOutputAvailable || encoderOutputAvailable) {
checkConversionCanceled();
int encoderStatus = encoder.dequeueOutputBuffer(info, increaseTimeout ? MEDIACODEC_TIMEOUT_INCREASED : MEDIACODEC_TIMEOUT_DEFAULT);
if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
encoderOutputAvailable = false;
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
if (Build.VERSION.SDK_INT < 21) {
encoderOutputBuffers = encoder.getOutputBuffers();
}
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
MediaFormat newFormat = encoder.getOutputFormat();
if (videoTrackIndex == -5 && newFormat != null) {
videoTrackIndex = mediaMuxer.addTrack(newFormat, false);
if (newFormat.containsKey(MediaFormat.KEY_PREPEND_HEADER_TO_SYNC_FRAMES) && newFormat.getInteger(MediaFormat.KEY_PREPEND_HEADER_TO_SYNC_FRAMES) == 1) {
ByteBuffer spsBuff = newFormat.getByteBuffer("csd-0");
ByteBuffer ppsBuff = newFormat.getByteBuffer("csd-1");
prependHeaderSize = spsBuff.limit() + ppsBuff.limit();
}
}
} else if (encoderStatus < 0) {
throw new RuntimeException("unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus);
} else {
ByteBuffer encodedData;
if (Build.VERSION.SDK_INT < 21) {
encodedData = encoderOutputBuffers[encoderStatus];
} else {
encodedData = encoder.getOutputBuffer(encoderStatus);
}
if (encodedData == null) {
throw new RuntimeException("encoderOutputBuffer " + encoderStatus + " was null");
}
if (info.size > 1) {
if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
if (prependHeaderSize != 0 && (info.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) {
info.offset += prependHeaderSize;
info.size -= prependHeaderSize;
}
if (firstEncode && (info.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) {
if (info.size > 100) {
encodedData.position(info.offset);
byte[] temp = new byte[100];
encodedData.get(temp);
int nalCount = 0;
for (int a = 0; a < temp.length - 4; a++) {
if (temp[a] == 0 && temp[a + 1] == 0 && temp[a + 2] == 0 && temp[a + 3] == 1) {
nalCount++;
if (nalCount > 1) {
info.offset += a;
info.size -= a;
break;
}
}
}
}
firstEncode = false;
}
long availableSize = mediaMuxer.writeSampleData(videoTrackIndex, encodedData, info, true);
if (availableSize != 0) {
if (callback != null) {
if (info.presentationTimeUs - startTime > currentPts) {
currentPts = info.presentationTimeUs - startTime;
}
callback.didWriteData(availableSize, (currentPts / 1000f) / durationS);
}
}
} else if (videoTrackIndex == -5) {
byte[] csd = new byte[info.size];
encodedData.limit(info.offset + info.size);
encodedData.position(info.offset);
encodedData.get(csd);
ByteBuffer sps = null;
ByteBuffer pps = null;
for (int a = info.size - 1; a >= 0; a--) {
if (a > 3) {
if (csd[a] == 1 && csd[a - 1] == 0 && csd[a - 2] == 0 && csd[a - 3] == 0) {
sps = ByteBuffer.allocate(a - 3);
pps = ByteBuffer.allocate(info.size - (a - 3));
sps.put(csd, 0, a - 3).position(0);
pps.put(csd, a - 3, info.size - (a - 3)).position(0);
break;
}
} else {
break;
}
}
MediaFormat newFormat = MediaFormat.createVideoFormat(MediaController.VIDEO_MIME_TYPE, w, h);
if (sps != null && pps != null) {
newFormat.setByteBuffer("csd-0", sps);
newFormat.setByteBuffer("csd-1", pps);
}
videoTrackIndex = mediaMuxer.addTrack(newFormat, false);
}
}
outputDone = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
encoder.releaseOutputBuffer(encoderStatus, false);
}
if (encoderStatus != MediaCodec.INFO_TRY_AGAIN_LATER) {
continue;
}
if (!decoderDone) {
int decoderStatus = decoder.dequeueOutputBuffer(info, MEDIACODEC_TIMEOUT_DEFAULT);
if (decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
decoderOutputAvailable = false;
} else if (decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
} else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
MediaFormat newFormat = decoder.getOutputFormat();
if (BuildVars.LOGS_ENABLED) {
FileLog.d("newFormat = " + newFormat);
}
} else if (decoderStatus < 0) {
throw new RuntimeException("unexpected result from decoder.dequeueOutputBuffer: " + decoderStatus);
} else {
boolean doRender = info.size != 0;
long originalPresentationTime = info.presentationTimeUs;
if (endTime > 0 && originalPresentationTime >= endTime) {
inputDone = true;
decoderDone = true;
doRender = false;
info.flags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM;
}
boolean flushed = false;
if (avatarStartTime >= 0 && (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0 && Math.abs(avatarStartTime - startTime) > 1000000 / framerate) {
if (startTime > 0) {
extractor.seekTo(startTime, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
} else {
extractor.seekTo(0, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
}
additionalPresentationTime = minPresentationTime + frameDelta;
endTime = avatarStartTime;
avatarStartTime = -1;
inputDone = false;
decoderDone = false;
doRender = false;
info.flags &= ~MediaCodec.BUFFER_FLAG_END_OF_STREAM;
decoder.flush();
flushed = true;
}
if (lastFramePts > 0 && info.presentationTimeUs - lastFramePts < frameDeltaFroSkipFrames && (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) == 0) {
doRender = false;
}
trueStartTime = avatarStartTime >= 0 ? avatarStartTime : startTime;
if (trueStartTime > 0 && videoTime == -1) {
if (originalPresentationTime < trueStartTime) {
doRender = false;
if (BuildVars.LOGS_ENABLED) {
FileLog.d("drop frame startTime = " + trueStartTime + " present time = " + info.presentationTimeUs);
}
} else {
videoTime = info.presentationTimeUs;
if (minPresentationTime != Integer.MIN_VALUE) {
additionalPresentationTime -= videoTime;
}
}
}
if (flushed) {
videoTime = -1;
} else {
if (avatarStartTime == -1 && additionalPresentationTime != 0) {
info.presentationTimeUs += additionalPresentationTime;
}
decoder.releaseOutputBuffer(decoderStatus, doRender);
}
if (doRender) {
lastFramePts = info.presentationTimeUs;
if (avatarStartTime >= 0) {
minPresentationTime = Math.max(minPresentationTime, info.presentationTimeUs);
}
boolean errorWait = false;
try {
outputSurface.awaitNewImage();
} catch (Exception e) {
errorWait = true;
FileLog.e(e);
}
if (!errorWait) {
outputSurface.drawImage();
inputSurface.setPresentationTime(info.presentationTimeUs * 1000);
inputSurface.swapBuffers();
}
}
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
decoderOutputAvailable = false;
if (BuildVars.LOGS_ENABLED) {
FileLog.d("decoder stream end");
}
encoder.signalEndOfInputStream();
}
}
}
}
}
} catch (Exception e) {
// in some case encoder.dequeueOutputBuffer return IllegalStateException
// stable reproduced on xiaomi
// fix it by increasing timeout
if (e instanceof IllegalStateException && !increaseTimeout) {
repeatWithIncreasedTimeout = true;
}
FileLog.e("bitrate: " + bitrate + " framerate: " + framerate + " size: " + resultHeight + "x" + resultWidth);
FileLog.e(e);
error = true;
}
extractor.unselectTrack(videoIndex);
if (decoder != null) {
decoder.stop();
decoder.release();
}
}
if (outputSurface != null) {
outputSurface.release();
}
if (inputSurface != null) {
inputSurface.release();
}
if (encoder != null) {
encoder.stop();
encoder.release();
}
if (audioRecoder != null) {
audioRecoder.release();
}
checkConversionCanceled();
} else {
readAndWriteTracks(extractor, mediaMuxer, info, startTime, endTime, duration, cacheFile, bitrate != -1);
}
}
} catch (Throwable e) {
error = true;
FileLog.e("bitrate: " + bitrate + " framerate: " + framerate + " size: " + resultHeight + "x" + resultWidth);
FileLog.e(e);
} finally {
if (extractor != null) {
extractor.release();
}
if (mediaMuxer != null) {
try {
mediaMuxer.finishMovie();
endPresentationTime = mediaMuxer.getLastFrameTimestamp(videoTrackIndex);
} catch (Throwable e) {
FileLog.e(e);
}
}
}
if (repeatWithIncreasedTimeout) {
return convertVideoInternal(videoPath, cacheFile, rotationValue, isSecret,
originalWidth, originalHeight,
resultWidth, resultHeight, framerate, bitrate, originalBitrate, startTime, endTime, avatarStartTime, duration,
needCompress, true, savedFilterState, paintPath, mediaEntities,
isPhoto, cropState, isRound);
}
long timeLeft = System.currentTimeMillis() - time;
if (BuildVars.LOGS_ENABLED) {
FileLog.d("compression completed time=" + timeLeft + " needCompress=" + needCompress + " w=" + resultWidth + " h=" + resultHeight + " bitrate=" + bitrate + " file size=" + cacheFile.length());
}
return error;
}
private long readAndWriteTracks(MediaExtractor extractor, MP4Builder mediaMuxer,
MediaCodec.BufferInfo info, long start, long end, long duration, File file, boolean needAudio) throws Exception {
int videoTrackIndex = MediaController.findTrack(extractor, false);
int audioTrackIndex = needAudio ? MediaController.findTrack(extractor, true) : -1;
int muxerVideoTrackIndex = -1;
int muxerAudioTrackIndex = -1;
boolean inputDone = false;
long currentPts = 0;
float durationS = duration / 1000f;
int maxBufferSize = 0;
if (videoTrackIndex >= 0) {
extractor.selectTrack(videoTrackIndex);
MediaFormat trackFormat = extractor.getTrackFormat(videoTrackIndex);
muxerVideoTrackIndex = mediaMuxer.addTrack(trackFormat, false);
try {
maxBufferSize = trackFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
} catch (Exception e) {
FileLog.e(e); //s20 ultra exception
}
if (start > 0) {
extractor.seekTo(start, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
} else {
extractor.seekTo(0, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
}
}
if (audioTrackIndex >= 0) {
extractor.selectTrack(audioTrackIndex);
MediaFormat trackFormat = extractor.getTrackFormat(audioTrackIndex);
if (trackFormat.getString(MediaFormat.KEY_MIME).equals("audio/unknown")) {
audioTrackIndex = -1;
} else {
muxerAudioTrackIndex = mediaMuxer.addTrack(trackFormat, true);
try {
maxBufferSize = Math.max(trackFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE), maxBufferSize);
} catch (Exception e) {
FileLog.e(e); //s20 ultra exception
}
if (start > 0) {
extractor.seekTo(start, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
} else {
extractor.seekTo(0, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
}
}
}
if (maxBufferSize <= 0) {
maxBufferSize = 64 * 1024;
}
ByteBuffer buffer = ByteBuffer.allocateDirect(maxBufferSize);
if (audioTrackIndex >= 0 || videoTrackIndex >= 0) {
long startTime = -1;
checkConversionCanceled();
while (!inputDone) {
checkConversionCanceled();
boolean eof = false;
int muxerTrackIndex;
if (Build.VERSION.SDK_INT >= 28) {
long size = extractor.getSampleSize();
if (size > maxBufferSize) {
maxBufferSize = (int) (size + 1024);
buffer = ByteBuffer.allocateDirect(maxBufferSize);
}
}
info.size = extractor.readSampleData(buffer, 0);
int index = extractor.getSampleTrackIndex();
if (index == videoTrackIndex) {
muxerTrackIndex = muxerVideoTrackIndex;
} else if (index == audioTrackIndex) {
muxerTrackIndex = muxerAudioTrackIndex;
} else {
muxerTrackIndex = -1;
}
if (muxerTrackIndex != -1) {
if (Build.VERSION.SDK_INT < 21) {
buffer.position(0);
buffer.limit(info.size);
}
if (index != audioTrackIndex) {
byte[] array = buffer.array();
if (array != null) {
int offset = buffer.arrayOffset();
int len = offset + buffer.limit();
int writeStart = -1;
for (int a = offset; a <= len - 4; a++) {
if (array[a] == 0 && array[a + 1] == 0 && array[a + 2] == 0 && array[a + 3] == 1 || a == len - 4) {
if (writeStart != -1) {
int l = a - writeStart - (a != len - 4 ? 4 : 0);
array[writeStart] = (byte) (l >> 24);
array[writeStart + 1] = (byte) (l >> 16);
array[writeStart + 2] = (byte) (l >> 8);
array[writeStart + 3] = (byte) l;
writeStart = a;
} else {
writeStart = a;
}
}
}
}
}
if (info.size >= 0) {
info.presentationTimeUs = extractor.getSampleTime();
} else {
info.size = 0;
eof = true;
}
if (info.size > 0 && !eof) {
if (index == videoTrackIndex && start > 0 && startTime == -1) {
startTime = info.presentationTimeUs;
}
if (end < 0 || info.presentationTimeUs < end) {
info.offset = 0;
info.flags = extractor.getSampleFlags();
long availableSize = mediaMuxer.writeSampleData(muxerTrackIndex, buffer, info, false);
if (availableSize != 0) {
if (callback != null) {
if (info.presentationTimeUs - startTime > currentPts) {
currentPts = info.presentationTimeUs - startTime;
}
callback.didWriteData(availableSize, (currentPts / 1000f) / durationS);
}
}
} else {
eof = true;
}
}
if (!eof) {
extractor.advance();
}
} else if (index == -1) {
eof = true;
} else {
extractor.advance();
}
if (eof) {
inputDone = true;
}
}
if (videoTrackIndex >= 0) {
extractor.unselectTrack(videoTrackIndex);
}
if (audioTrackIndex >= 0) {
extractor.unselectTrack(audioTrackIndex);
}
return startTime;
}
return -1;
}
private void checkConversionCanceled() {
if (callback != null && callback.checkConversionCanceled())
throw new ConversionCanceledException();
}
private static String createFragmentShader(
final int srcWidth,
final int srcHeight,
final int dstWidth,
final int dstHeight, boolean external) {
final float kernelSize = Utilities.clamp((float) (Math.max(srcWidth, srcHeight) / (float) Math.max(dstHeight, dstWidth)) * 0.8f, 2f, 1f);
final int kernelRadius = (int) kernelSize;
FileLog.d("source size " + srcWidth + "x" + srcHeight + " dest size " + dstWidth + dstHeight + " kernelRadius " + kernelRadius);
if (external) {
return "#extension GL_OES_EGL_image_external : require\n" +
"precision mediump float;\n" +
"varying vec2 vTextureCoord;\n" +
"const float kernel = " + kernelRadius + ".0;\n" +
"const float pixelSizeX = 1.0 / " + srcWidth + ".0;\n" +
"const float pixelSizeY = 1.0 / " + srcHeight + ".0;\n" +
"uniform samplerExternalOES sTexture;\n" +
"void main() {\n" +
"vec3 accumulation = vec3(0);\n" +
"vec3 weightsum = vec3(0);\n" +
"for (float x = -kernel; x <= kernel; x++){\n" +
" for (float y = -kernel; y <= kernel; y++){\n" +
" accumulation += texture2D(sTexture, vTextureCoord + vec2(x * pixelSizeX, y * pixelSizeY)).xyz;\n" +
" weightsum += 1.0;\n" +
" }\n" +
"}\n" +
"gl_FragColor = vec4(accumulation / weightsum, 1.0);\n" +
"}\n";
} else {
return "precision mediump float;\n" +
"varying vec2 vTextureCoord;\n" +
"const float kernel = " + kernelRadius + ".0;\n" +
"const float pixelSizeX = 1.0 / " + srcHeight + ".0;\n" +
"const float pixelSizeY = 1.0 / " + srcWidth + ".0;\n" +
"uniform sampler2D sTexture;\n" +
"void main() {\n" +
"vec3 accumulation = vec3(0);\n" +
"vec3 weightsum = vec3(0);\n" +
"for (float x = -kernel; x <= kernel; x++){\n" +
" for (float y = -kernel; y <= kernel; y++){\n" +
" accumulation += texture2D(sTexture, vTextureCoord + vec2(x * pixelSizeX, y * pixelSizeY)).xyz;\n" +
" weightsum += 1.0;\n" +
" }\n" +
"}\n" +
"gl_FragColor = vec4(accumulation / weightsum, 1.0);\n" +
"}\n";
}
}
public class ConversionCanceledException extends RuntimeException {
public ConversionCanceledException() {
super("canceled conversion");
}
}
}