还是废了蛮多劲头,查了很多资料,终于能获取所有视频帧的数据了 依赖一些简单工具类,可以注释掉 还有一些不完善之处,比如如何指定解码宽高的,希望大神能指教 见代码
package a.baozouptu.editvideo.track;
import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible;
import android.annotation.SuppressLint;
import android.graphics.ImageFormat;
import android.media.ImageReader;
import android.media.MediaCodec;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.baozou.ptu.baselibrary.utils.LogUtil;
import com.baozou.ptu.baselibrary.utils.SegmentedProgressCallback;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.util.Locale;
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class VideoFrameDecoder extends HandlerThread {
public static final String TAG = "VideoFrameDecoder";
private long intervalMs = 0;
private boolean requestStop = false;
private boolean initSuccess = false;
@Nullable
private final SegmentedProgressCallback progressCallback;
@NotNull
private final DecoderCallBack decoderCallBack;
private MediaExtractor extractor;
private MediaCodec mediaCodec;
private ImageReader imageReader;
private long lastPresentationTimeUs;
private FrameDecoder frameDecoder;
private final Handler handler;
private ImageReaderHandlerThread imageReaderHandlerThread;
private long durationUs;
private int rotation;
private int videoW;
private int videoH;
private int fps;
private String colorFormatName = "no data";
private int colorRange = -1;
public VideoFrameDecoder(@NotNull DecoderCallBack decoderCallBack,
@Nullable SegmentedProgressCallback progressCallback) {
super(TAG);
start();
Looper looper = getLooper();
handler = new Handler(looper);
this.progressCallback = progressCallback;
this.decoderCallBack = decoderCallBack;
extractor = new MediaExtractor();
this.frameDecoder = new FrameDecoder();
LogUtil.logTrack(TAG + " 创建完成");
}
public void setVideoAndInitAsync(final String fileName) {
initSuccess = false;
handler.post(() -> {
LogUtil.logTrack("开始获取视频信息 \n 当前线程是: " + Thread.currentThread().getName());
if (progressCallback != null)
progressCallback.onProgress(progressCallback.seg + 1, "正在获取视频信息");
try {
extractor.setDataSource(fileName);
MediaFormat videoFormat = extractVideoInfo();
if (videoFormat == null) {
decoderCallBack.onError(new Exception("无法获取视频信息"));
return;
}
int dstW = videoW, dstH = videoH;
videoFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, COLOR_FormatYUV420Flexible);
videoFormat.setInteger(MediaFormat.KEY_WIDTH, dstW);
videoFormat.setInteger(MediaFormat.KEY_HEIGHT, dstH);
mediaCodec = MediaCodec.createDecoderByType(videoFormat.getString(MediaFormat.KEY_MIME));
int imageFormat = ImageFormat.YUV_420_888;
imageReader = ImageReader.newInstance(
dstW, dstH,
imageFormat,
1
);
mediaCodec.configure(videoFormat, imageReader.getSurface(), null, 0);
mediaCodec.start();
LogUtil.logTrack("获取视频信息和初始化完成 当前线程是: " + Thread.currentThread().getName());
LogUtil.logTrack(String.format(Locale.CHINA,
"视频信息:width = %d height = %d, 时长 = %ds fps = %d 旋转 = %d" +
"颜色格式 = %s 颜色范围 = %d",
videoW, videoH, durationUs, fps, rotation,
colorFormatName, colorRange));
initSuccess = true;
decoderCallBack.onInitVideoSuccess();
} catch (Exception e) {
initSuccess = false;
e.printStackTrace();
LogUtil.logTrack("init 视频解码器出错 \n 当前线程是: " + Thread.currentThread().getName());
decoderCallBack.onError(new Exception("初始化视频解码器失败" + e.getMessage()));
}
});
}
private MediaFormat extractVideoInfo() {
MediaFormat videoFormat = null;
int trackCount = extractor.getTrackCount();
for (int i = 0; i < trackCount; i++) {
MediaFormat trackFormat = extractor.getTrackFormat(i);
if (trackFormat.getString(MediaFormat.KEY_MIME).contains("video")) {
videoFormat = trackFormat;
extractor.selectTrack(i);
break;
}
}
if (videoFormat == null) {
LogUtil.logTrack("无法获取视频信息 \n 当前线程是: " + Thread.currentThread().getName());
return null;
} else {
LogUtil.logTrack("init MediaExtractor finish \n 当前线程是: " + Thread.currentThread().getName());
}
videoW = videoFormat.getInteger(MediaFormat.KEY_WIDTH);
videoH = videoFormat.getInteger(MediaFormat.KEY_HEIGHT);
if (videoFormat.containsKey(MediaFormat.KEY_ROTATION) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
rotation = videoFormat.getInteger(MediaFormat.KEY_ROTATION);
}
durationUs = videoFormat.getLong(MediaFormat.KEY_DURATION);
if (videoFormat.containsKey(MediaFormat.KEY_FRAME_RATE))
fps = videoFormat.getInteger(MediaFormat.KEY_FRAME_RATE);
if (videoFormat.containsKey(MediaFormat.KEY_COLOR_RANGE) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
colorRange = videoFormat.getInteger(MediaFormat.KEY_COLOR_RANGE);
}
if (videoFormat.containsKey(MediaFormat.KEY_ROTATION)) {
colorFormatName = getColorFormatName(videoFormat.getInteger(MediaFormat.KEY_COLOR_FORMAT));
}
return videoFormat;
}
public void startDecode() {
if (!initSuccess) {
decoderCallBack.onError(new Exception("初始化失败,无法解码,请检查"));
return;
}
imageReaderHandlerThread = new ImageReaderHandlerThread();
handler.post(() -> {
LogUtil.logTrack("开始解码 当前线程是: \" + Thread.currentThread().getName()");
if (progressCallback != null) {
progressCallback.addSeg("正在解码...");
}
imageReader.setOnImageAvailableListener(new MyOnImageAvailableListener(),
imageReaderHandlerThread.getHandler());
extractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
frameDecoder.decodeOneFrame();
});
}
private class FrameDecoder {
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
long timeOutUs = 5 * 1000;
boolean inputFinish = false;
boolean outputDone = false;
int count = 1;
public void decodeOneFrame() {
LogUtil.logTrack("开始解码一帧,当前线程是: " + Thread.currentThread().getName());
try {
if (outputDone) return;
boolean hasDecodeFrameFinish = false;
while (!hasDecodeFrameFinish) {
if (requestStop) {
return;
}
if (!inputFinish) {
int inputBufferIndex = mediaCodec.dequeueInputBuffer(timeOutUs);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer;
inputBuffer = mediaCodec.getInputBuffer(inputBufferIndex);
int sampleData = extractor.readSampleData(inputBuffer, 0);
if (sampleData > 0) {
long sampleTime = extractor.getSampleTime();
LogUtil.logTrack(String.format(Locale.CHINA, "解码到的时间 = %.4fs",
sampleTime / 1000000f));
mediaCodec.queueInputBuffer(inputBufferIndex, 0, sampleData, sampleTime, 0);
if (intervalMs == 0) {
extractor.advance();
} else {
if (count * intervalMs * 1000 > durationUs) {
extractor.advance();
} else {
extractor.seekTo(count * intervalMs * 1000, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
count++;
}
}
} else {
mediaCodec.queueInputBuffer(inputBufferIndex, 0, 0, 0L,
MediaCodec.BUFFER_FLAG_END_OF_STREAM);
inputFinish = true;
LogUtil.logTrack("解码输入数据完成 \n 当前线程是: " + Thread.currentThread().getName());
if (progressCallback != null) {
progressCallback.onProgress(progressCallback.progress + 1 / 1000f);
}
}
}
}
if (!outputDone) {
int status = mediaCodec.dequeueOutputBuffer(bufferInfo, timeOutUs);
if (status ==
MediaCodec.INFO_TRY_AGAIN_LATER) {
} else if (status == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
} else if (status == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
} else {
if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
LogUtil.logTrack("解码结束 output EOS");
outputDone = true;
}
boolean doRender = (bufferInfo.size != 0);
long presentationTimeUs = bufferInfo.presentationTimeUs;
if (lastPresentationTimeUs == 0) {
lastPresentationTimeUs = presentationTimeUs;
} else {
long diff = presentationTimeUs - lastPresentationTimeUs;
if (intervalMs != 0) {
if (diff / 1000 < (intervalMs - 10)) {
long lastDiff = durationUs - presentationTimeUs;
LogUtil.logTrack("duration=" + durationUs + ", lastDiff=" + lastDiff);
if (lastDiff < 50 * 1000 && diff > 0) {
lastPresentationTimeUs = durationUs;
} else {
doRender = false;
}
} else {
lastPresentationTimeUs = presentationTimeUs;
}
LogUtil.logTrack(
"diff time in ms =" + diff / 1000);
}
}
if (progressCallback != null) {
progressCallback.onProgress(progressCallback.progress + 1 / 1000f);
}
mediaCodec.releaseOutputBuffer(status, doRender);
hasDecodeFrameFinish = true;
LogUtil.logTrack("解码一帧完成,等待消费获取");
}
}
}
} catch (Exception e) {
String message = "解码过程出错,已停止解码 " + e.getMessage();
decoderCallBack.onError(new Exception(message));
LogUtil.logTrack(message);
e.printStackTrace();
}
}
public boolean isFinish() {
return outputDone;
}
public void release() {
if (mediaCodec != null) {
mediaCodec.stop();
mediaCodec.release();
}
if (extractor != null) {
extractor.release();
}
}
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
private class MyOnImageAvailableListener implements ImageReader.OnImageAvailableListener {
private int frameNumber = 0;
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public void onImageAvailable(ImageReader reader) {
frameNumber++;
LogUtil.logTrack("");
LogUtil.logTrack("---------------------------------------------------------------------------------");
LogUtil.logTrack("收到视频帧解码第 " + frameNumber +
" 帧回调" + Thread.currentThread().getName());
LogUtil.logTrack("---------------------------------------------------------------------------------");
LogUtil.logTrack("");
decoderCallBack.syncProcessFrame(reader, frameNumber, rotation);
if (!frameDecoder.isFinish()) {
handler.post(() -> frameDecoder.decodeOneFrame());
} else {
frameDecoder.release();
decoderCallBack.onDecodeFinish();
}
}
}
@SuppressLint("PrivateApi")
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
String getColorFormatName(int formatValue) {
Class clazz = null;
try {
clazz = Class.forName("android.media.ScoreTable.CodecCapabilities");
Field[] fields = clazz.getFields();
for (Field field : fields) {
if (field.getInt(clazz) == formatValue)
return field.getName();
}
} catch (ClassNotFoundException | IllegalAccessException e) {
e.printStackTrace();
}
return "error on get colorformat name";
}
private static class ImageReaderHandlerThread extends HandlerThread {
private final Handler handler;
public ImageReaderHandlerThread() {
super("ImageReader");
start();
Looper looper = getLooper();
handler = new Handler(looper);
}
public Handler getHandler() {
return handler;
}
}
private void _release() {
LogUtil.logTrack("销毁解码器对象");
requestStop = true;
if (imageReader != null) {
imageReader.close();
imageReader = null;
}
if (mediaCodec != null) {
mediaCodec.stop();
mediaCodec.release();
mediaCodec = null;
}
if (extractor != null) {
extractor.release();
extractor = null;
}
}
public interface DecoderCallBack {
void syncProcessFrame(ImageReader frame, int frameId, int rotation);
void onFrameError(int frameId, Exception e);
void onError(Exception e);
void onInitVideoSuccess();
void onDecodeFinish();
}
}
|