package org.telegram.messenger.video; import android.media.MediaCodec; import android.media.MediaCodecInfo; import android.media.MediaExtractor; import android.media.MediaFormat; import android.os.Build; import org.telegram.messenger.BuildVars; import org.telegram.messenger.FileLog; import org.telegram.messenger.MediaController; import org.telegram.messenger.Utilities; import java.io.File; import java.nio.ByteBuffer; public class MediaCodecVideoConvertor { MP4Builder mediaMuxer; MediaExtractor extractor; 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 resultWidth, int resultHeight, int framerate, int bitrate, long startTime, long endTime, boolean needCompress, long duration, MediaController.VideoConvertorListener callback) { this.callback = callback; return convertVideoInternal(videoPath, cacheFile, rotationValue, isSecret, resultWidth, resultHeight, framerate, bitrate, startTime, endTime, duration, needCompress, false); } private boolean convertVideoInternal(String videoPath, File cacheFile, int rotationValue, boolean isSecret, int resultWidth, int resultHeight, int framerate, int bitrate, long startTime, long endTime, long duration, boolean needCompress, boolean increaseTimeout) { boolean error = false; boolean repeatWithIncreasedTimeout = false; try { MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); Mp4Movie movie = new Mp4Movie(); movie.setCacheFile(cacheFile); movie.setRotation(rotationValue); movie.setSize(resultWidth, resultHeight); mediaMuxer = new MP4Builder().createMovie(movie, isSecret); extractor = new MediaExtractor(); extractor.setDataSource(videoPath); long currentPts = 0; float durationS = duration / 1000f; checkConversionCanceled(); if (needCompress) { int videoIndex = MediaController.findTrack(extractor, false); int audioIndex = bitrate != -1 ? MediaController.findTrack(extractor, true) : -1; AudioRecoder audioRecoder = null; ByteBuffer audioBuffer = null; boolean copyAudioBuffer = true; if (videoIndex >= 0) { MediaCodec decoder = null; MediaCodec encoder = null; InputSurface inputSurface = null; OutputSurface outputSurface = null; int prependHeaderSize = 0; try { long videoTime = -1; boolean outputDone = false; boolean inputDone = false; boolean decoderDone = false; int swapUV = 0; int videoTrackIndex = -5; int audioTrackIndex = -5; int colorFormat; int processorType = PROCESSOR_TYPE_OTHER; String manufacturer = Build.MANUFACTURER.toLowerCase(); if (Build.VERSION.SDK_INT < 18) { MediaCodecInfo codecInfo = MediaController.selectCodec(MediaController.VIDEO_MIME_TYPE); colorFormat = MediaController.selectColorFormat(codecInfo, MediaController.VIDEO_MIME_TYPE); if (colorFormat == 0) { throw new RuntimeException("no supported color format"); } String codecName = codecInfo.getName(); if (codecName.contains("OMX.qcom.")) { processorType = PROCESSOR_TYPE_QCOM; if (Build.VERSION.SDK_INT == 16) { if (manufacturer.equals("lge") || manufacturer.equals("nokia")) { swapUV = 1; } } } else if (codecName.contains("OMX.Intel.")) { processorType = PROCESSOR_TYPE_INTEL; } else if (codecName.equals("OMX.MTK.VIDEO.ENCODER.AVC")) { processorType = PROCESSOR_TYPE_MTK; } else if (codecName.equals("OMX.SEC.AVC.Encoder")) { processorType = PROCESSOR_TYPE_SEC; swapUV = 1; } else if (codecName.equals("OMX.TI.DUCATI1.VIDEO.H264E")) { processorType = PROCESSOR_TYPE_TI; } if (BuildVars.LOGS_ENABLED) { FileLog.d("codec = " + codecInfo.getName() + " manufacturer = " + manufacturer + "device = " + Build.MODEL); } } else { colorFormat = MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface; } if (BuildVars.LOGS_ENABLED) { FileLog.d("colorFormat = " + colorFormat); } int resultHeightAligned = resultHeight; int padding = 0; int bufferSize = resultWidth * resultHeight * 3 / 2; if (processorType == PROCESSOR_TYPE_OTHER) { if (resultHeight % 16 != 0) { resultHeightAligned += (16 - (resultHeight % 16)); padding = resultWidth * (resultHeightAligned - resultHeight); bufferSize += padding * 5 / 4; } } else if (processorType == PROCESSOR_TYPE_QCOM) { if (!manufacturer.toLowerCase().equals("lge")) { int uvoffset = (resultWidth * resultHeight + 2047) & ~2047; padding = uvoffset - (resultWidth * resultHeight); bufferSize += padding; } } else if (processorType == PROCESSOR_TYPE_TI) { //resultHeightAligned = 368; //bufferSize = resultWidth * resultHeightAligned * 3 / 2; //resultHeightAligned += (16 - (resultHeight % 16)); //padding = resultWidth * (resultHeightAligned - resultHeight); //bufferSize += padding * 5 / 4; } else if (processorType == PROCESSOR_TYPE_MTK) { if (manufacturer.equals("baidu")) { resultHeightAligned += (16 - (resultHeight % 16)); padding = resultWidth * (resultHeightAligned - resultHeight); bufferSize += padding * 5 / 4; } } extractor.selectTrack(videoIndex); MediaFormat videoFormat = extractor.getTrackFormat(videoIndex); if (startTime > 0) { extractor.seekTo(startTime, MediaExtractor.SEEK_TO_PREVIOUS_SYNC); } else { extractor.seekTo(0, MediaExtractor.SEEK_TO_PREVIOUS_SYNC); } if (bitrate <= 0) bitrate = 921600; MediaFormat outputFormat = MediaFormat.createVideoFormat(MediaController.VIDEO_MIME_TYPE, resultWidth, resultHeight); outputFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat); 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(resultHeight, resultWidth) <= 480) { if (bitrate > 921600) bitrate = 921600; outputFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate); } if (Build.VERSION.SDK_INT < 18) { outputFormat.setInteger("stride", resultWidth + 32); outputFormat.setInteger("slice-height", resultHeight); } encoder = MediaCodec.createEncoderByType(MediaController.VIDEO_MIME_TYPE); encoder.configure(outputFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); if (Build.VERSION.SDK_INT >= 18) { inputSurface = new InputSurface(encoder.createInputSurface()); inputSurface.makeCurrent(); } encoder.start(); decoder = MediaCodec.createDecoderByType(videoFormat.getString(MediaFormat.KEY_MIME)); if (Build.VERSION.SDK_INT >= 18) { outputSurface = new OutputSurface(); } else { outputSurface = new OutputSurface(resultWidth, resultHeight, rotationValue); } 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(); if (Build.VERSION.SDK_INT < 18) { encoderInputBuffers = encoder.getInputBuffers(); } } if (audioIndex >= 0) { MediaFormat audioFormat = extractor.getTrackFormat(audioIndex); copyAudioBuffer = audioFormat.getString(MediaFormat.KEY_MIME).equals(MediaController.AUIDO_MIME_TYPE); audioTrackIndex = mediaMuxer.addTrack(audioFormat, true); if (copyAudioBuffer) { extractor.selectTrack(audioIndex); int maxBufferSize = audioFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE); 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; } } boolean audioEncoderDone = false; 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) { 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, 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) { 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; if (Build.VERSION.SDK_INT >= 18) { doRender = info.size != 0; } else { doRender = info.size != 0 || info.presentationTimeUs != 0; } if (endTime > 0 && info.presentationTimeUs >= endTime) { inputDone = true; decoderDone = true; doRender = false; info.flags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM; } if (startTime > 0 && videoTime == -1) { if (info.presentationTimeUs < startTime) { doRender = false; if (BuildVars.LOGS_ENABLED) { FileLog.d("drop frame startTime = " + startTime + " present time = " + info.presentationTimeUs); } } else { videoTime = info.presentationTimeUs; } } decoder.releaseOutputBuffer(decoderStatus, doRender); if (doRender) { boolean errorWait = false; try { outputSurface.awaitNewImage(); } catch (Exception e) { errorWait = true; FileLog.e(e); } if (!errorWait) { if (Build.VERSION.SDK_INT >= 18) { outputSurface.drawImage(false); inputSurface.setPresentationTime(info.presentationTimeUs * 1000); inputSurface.swapBuffers(); } else { int inputBufIndex = encoder.dequeueInputBuffer(MEDIACODEC_TIMEOUT_DEFAULT); if (inputBufIndex >= 0) { outputSurface.drawImage(true); ByteBuffer rgbBuf = outputSurface.getFrame(); ByteBuffer yuvBuf = encoderInputBuffers[inputBufIndex]; yuvBuf.clear(); Utilities.convertVideoFrame(rgbBuf, yuvBuf, colorFormat, resultWidth, resultHeight, padding, swapUV); encoder.queueInputBuffer(inputBufIndex, 0, bufferSize, info.presentationTimeUs, 0); } else { if (BuildVars.LOGS_ENABLED) { FileLog.d("input buffer not available"); } } } } } if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { decoderOutputAvailable = false; if (BuildVars.LOGS_ENABLED) { FileLog.d("decoder stream end"); } if (Build.VERSION.SDK_INT >= 18) { encoder.signalEndOfInputStream(); } else { int inputBufIndex = encoder.dequeueInputBuffer(MEDIACODEC_TIMEOUT_DEFAULT); if (inputBufIndex >= 0) { encoder.queueInputBuffer(inputBufIndex, 0, 1, info.presentationTimeUs, MediaCodec.BUFFER_FLAG_END_OF_STREAM); } } } } } } } } 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 (outputSurface != null) { outputSurface.release(); } if (inputSurface != null) { inputSurface.release(); } if (decoder != null) { decoder.stop(); decoder.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 (Exception 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(); } catch (Exception e) { FileLog.e(e); } } } if (repeatWithIncreasedTimeout) { return convertVideoInternal(videoPath, cacheFile, rotationValue, isSecret, resultWidth, resultHeight, framerate, bitrate, startTime, endTime, duration, needCompress, true); } 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); maxBufferSize = trackFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE); 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); muxerAudioTrackIndex = mediaMuxer.addTrack(trackFormat, true); maxBufferSize = Math.max(trackFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE), maxBufferSize); if (start > 0) { extractor.seekTo(start, MediaExtractor.SEEK_TO_PREVIOUS_SYNC); } else { extractor.seekTo(0, MediaExtractor.SEEK_TO_PREVIOUS_SYNC); } } ByteBuffer buffer = ByteBuffer.allocateDirect(maxBufferSize); if (audioTrackIndex >= 0 || videoTrackIndex >= 0) { long startTime = -1; checkConversionCanceled(); while (!inputDone) { checkConversionCanceled(); boolean eof = false; int muxerTrackIndex; 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 RuntimeException("canceled conversion"); } }