时光倒流策略
时光倒流原理
把一个视频分为20帧一段(一个队列),从末尾开始显示(队列满20帧开始播放,具体队列大小可以调整)
1、创建两个队列,一个队列解码,一个队列播放,解码和播放分开两个线程
2、seek末尾开始解码存储最末尾队列达到满20帧(解码队列),满后转换到播放队列(解码队列全部出队),开始末尾帧播放
3、播放时候,如果解码线程判断解码队列是空的,seek到上一段队列开始存储第一个pts前20帧(最好方式提前获取视频gop每帧的pts能更准确seek)
4、按上面2、3方式反复就可以每帧倒流
说明:条件,获取时间总帧数(可以进行分段),获得每20一组的第一个帧时间戳(视频可能存在b帧所以可能无法获取,如果视频gop时间戳肯定是可以)(为了精确seek和保存队列)
环境 android系统
MediaExtractor、MediaCodec、ImageReader实现自己解码后yuv队列,按分辨率来设置队列大小防止崩溃(测试720p的可以20帧)
线程控制3条:MediaExtractor、MediaCodec实现解码视频线程;ImageReader是openGL获取yuv(mOnImageAvailableListener监听获取),videoRender渲染视频把yuv数据渲染SurfaceView的openGL上
/**
* 图像数据回调
* ImageReader这种方式时间有的长
*/
long csss=0;
private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
= new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
long cu=0;
if (csss==0){
csss=System.currentTimeMillis();
}else{
cu=System.currentTimeMillis()-csss;
csss=System.currentTimeMillis();
}
try (Image image = reader.acquireNextImage()) {
Image.Plane[] planes = image.getPlanes();
// for (int i = 0; i < planes.length; i++) {
// ByteBuffer iBuffer = planes[i].getBuffer();
// int iSize = iBuffer.remaining();
// Log.e(TAG, "pixelStride " + planes[i].getPixelStride());
// Log.e(TAG, "rowStride " + planes[i].getRowStride());
// Log.e(TAG, "width " + image.getWidth());
// Log.e(TAG, "height " + image.getHeight());
// Log.e(TAG, "Finished reading data from plane " + i);
// }
if (planes.length > 0) {
// ByteBuffer buffer = planes[0].getBuffer();
//数据格式转换 YUV_420 ===》NV21
long ss=System.currentTimeMillis();
byte[] data = ImageUtils.getDataFromImage2(image,1);
if (cache!=null){
synchronized (object) {
if (!cache.isNotSleep){
syncPlay=false;
}
cache.yuvData=data;
queue.offer(cache);
}
ss=System.currentTimeMillis()-ss;
Log.e("======",ss+"====="+cu+"=====cu===="+cache.getCurrentSampleTime());
cache=null;
}
}
}catch (Exception e){
}
notifymSync();
}
};
/**
* 刷新视频渲染
* @param videoCache 视频数据
* @param mediaNative nativewindow添数据
*/
private void videoRefresh(VideoCache videoCache,MediaNative mediaNative) {
if (videoCache != null&&videoCache.yuvData!=null) {
int outputBufferIndex = videoCache.getOutputBufferIndex();
long distant = videoCache.getDistant();
if (distant > 0&&!videoCache.isNotSleep) {
if (audioThread != null && mediaEditor.isHasAudio()) {
long dif = (audioThread.currentPosition - currentPosition) / AV_TIME_BASE;
if (audioThread.currentPosition < mediaEditor.getAudioDuration()) {
if (dif >= 20) {
distant = 0;
} else if (dif > 10) {
distant = 5;
}
}
}
try {
Thread.sleep(distant);
} catch (Exception e) {
e.printStackTrace();
}
}
if (outputBufferIndex >= 0) {
if (videoCache.render) {
mediaNative.setYUV(videoCache.yuvData, videoCache.strideHeiht,
videoCache.strideWidth, videoCache.height, videoCache.width);
}
}
}
}
时光倒流实现
时光倒流视频处理逻辑过程部分代码
class VideoDecodeThread extends Thread{
private volatile boolean threadRun=true;
private LinkedList<VideoCache> queue = new LinkedList<>();
private Queue<Integer> seekQueue = new LinkedList<>();
private long currentPosition = 0;
Object object = new Object();
Object objectQueue = new Object();
public volatile boolean isSeeking = false;
private boolean playing = false;
private MediaExtractor mediaExtractor;
private int sameSmc = -1;
private long duration;
public boolean isReleaseEnd;
private volatile boolean syncPlay;//视频解码比较慢同步视频播放第一帧同事开始
private ImageReader mImageReader;
private HandlerThread mImageReaderThread;
private Handler mImageReaderHandler;
protected final Object mSync = new Object();
private VideoCache cache;
public void notifymSync() {
synchronized (mSync) {
try {
mSync.notify();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 图像数据回调
* ImageReader这种方式时间有的长
*/
long csss=0;
private final ImageReader.OnImageAvailableListener mOnImageAvailableListener
= new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
long cu=0;
if (csss==0){
csss=System.currentTimeMillis();
}else{
cu=System.currentTimeMillis()-csss;
csss=System.currentTimeMillis();
}
try (Image image = reader.acquireNextImage()) {
Image.Plane[] planes = image.getPlanes();
// for (int i = 0; i < planes.length; i++) {
// ByteBuffer iBuffer = planes[i].getBuffer();
// int iSize = iBuffer.remaining();
// Log.e(TAG, "pixelStride " + planes[i].getPixelStride());
// Log.e(TAG, "rowStride " + planes[i].getRowStride());
// Log.e(TAG, "width " + image.getWidth());
// Log.e(TAG, "height " + image.getHeight());
// Log.e(TAG, "Finished reading data from plane " + i);
// }
if (planes.length > 0) {
// ByteBuffer buffer = planes[0].getBuffer();
//数据格式转换 YUV_420 ===》NV21
long ss=System.currentTimeMillis();
byte[] data = ImageUtils.getDataFromImage2(image,1);
if (cache!=null){
synchronized (object) {
if (!cache.isNotSleep){
syncPlay=false;
}
cache.yuvData=data;
queue.offer(cache);
}
ss=System.currentTimeMillis()-ss;
Log.e("======",ss+"====="+cu+"=====cu===="+cache.getCurrentSampleTime());
cache=null;
}
}
}catch (Exception e){
}
notifymSync();
}
};
private void prepareImageReader() {
mImageReaderThread = new HandlerThread("CameraThread");
mImageReaderThread.start();
mImageReaderHandler = new Handler(mImageReaderThread.getLooper());
mImageReader = ImageReader.newInstance(mediaEditor.getWidth(), mediaEditor.getHeight(),
ImageFormat.YUV_420_888, /* maxImages */ 2);
mImageReader.setOnImageAvailableListener(mOnImageAvailableListener, mImageReaderHandler);
}
/**
* 刷新视频渲染
* @param videoCache 视频数据
* @param mediaNative nativewindow添数据
*/
private void videoRefresh(VideoCache videoCache,MediaNative mediaNative) {
if (videoCache != null&&videoCache.yuvData!=null) {
int outputBufferIndex = videoCache.getOutputBufferIndex();
long distant = videoCache.getDistant();
if (distant > 0&&!videoCache.isNotSleep) {
if (audioThread != null && mediaEditor.isHasAudio()) {
long dif = (audioThread.currentPosition - currentPosition) / AV_TIME_BASE;
if (audioThread.currentPosition < mediaEditor.getAudioDuration()) {
if (dif >= 20) {
distant = 0;
} else if (dif > 10) {
distant = 5;
}
}
}
try {
Thread.sleep(distant);
} catch (Exception e) {
e.printStackTrace();
}
}
if (outputBufferIndex >= 0) {
if (videoCache.render) {
mediaNative.setYUV(videoCache.yuvData, videoCache.strideHeiht,
videoCache.strideWidth, videoCache.height, videoCache.width);
}
}
}
}
/**
* 刷新视频线程方法
*/
private void videoRender() {
videoRefreshHandler.post(new Runnable() {
@Override
public void run() {
while (surface == null) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (!threadRun) return;
}
while (threadRun) {
while (!queue.isEmpty()) {//seekTo优先级大于播放
if (!threadRun){
queue.clear();
break;
}
if (!playing&&!isSeeking) {//播放、暂停
try {
Thread.sleep(5);
} catch (InterruptedException e) {
}
continue;
}
VideoCache videoCache;
synchronized (objectQueue) {
videoCache = queue.poll();
}
if (videoCache==null){
continue;
}
if (!videoCache.isNotSleep){
currentPosition = videoCache.getCurrentSampleTime();
}
long audioTime=0;
if (audioThread!=null){
audioTime=audioThread.getCurrentPosition();
}
Log.e("=====",queue.size()+"==="+mediaEditor.getVideoDuration()+"=====videotime===="+getCurrentPosition()+"==="+audioTime);
videoRefresh( videoCache, mediaNative);
}
}
queue.clear();
}
});
}
public void seekTo(int msc) {
if (mediaEditor==null)return;
if (msc * AV_TIME_BASE > mediaEditor.getVideoDuration()) {
msc = (int) (mediaEditor.getVideoDuration() / AV_TIME_BASE);
}
if (sameSmc == msc || msc == currentPosition / AV_TIME_BASE) return;//去掉相同
sameSmc = msc;
currentPosition = msc * AV_TIME_BASE;
isSeeking = true;
synchronized (object) {
if (seekQueue.size() > 0) {
seekQueue.clear();
seekQueue.offer(msc);
} else {
seekQueue.offer(msc);
}
}
}
public void play() {
sameSmc = -1;
if (!playing){
syncPlay= true;
}
playing = true;
}
public void pause() {
sameSmc = -1;
playing = false;
}
public int getCurrentPosition() {
return (int) (currentPosition / AV_TIME_BASE);
}
public int getDuration() {
return (int) (duration / AV_TIME_BASE);
}
public boolean isPlaying() {
return playing;
}
public void release() {
threadRun = false;
notifymSync();
}
public boolean isReleaseEnd() {
return isReleaseEnd;
}
@Override
public void run() {
isReleaseEnd = false;
try {
videoDecode();
}catch (Exception e){
MediaCodeImageReaderPlayer.this.stop();
e.printStackTrace();
}
isReleaseEnd = true;
}
void videoDecode(){
threadRun = true;
mediaExtractor = mediaEditor.getVideoExtractor();
duration = mediaEditor.getDuration();
videoRender();
prepareImageReader();
MediaCodec mediaCodec = null;
try {
mediaCodec = MediaCodec.createDecoderByType(mediaEditor.getVideoMime());
mediaCodec.configure(mediaEditor.getVideoMediaFormat(),
mImageReader.getSurface(), null, 0);//flag=1的时候为encode
mediaCodec.start();
} catch (Exception e) {
e.printStackTrace();
}
if (mediaCodec==null)return;
MediaCodec.BufferInfo videoBufferInfo = new MediaCodec.BufferInfo();
final boolean[] readEnd = {false};
long lastSampleTime = 0;
int strideWidth = 0;
int strideHeiht = 0;
int keyColorFormat = 0;
boolean isDecode=false;
while (threadRun) {
while (!seekQueue.isEmpty()) {//seekTo优先级大于播放
if (!threadRun) break;
int msc = 0;
if (!queue.isEmpty()) {
synchronized (objectQueue) {
queue.clear();
}
}
synchronized (object) {
msc = seekQueue.poll();
}
if (msc * AV_TIME_BASE > mediaEditor.getVideoDuration()) {
msc = (int) (mediaEditor.getVideoDuration() / AV_TIME_BASE);
}
Log.e("========", "============msc========" + msc);
mediaExtractor.seekTo(msc * AV_TIME_BASE, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
readEnd[0] = false;
if (isDecode){//鸿蒙系统部分视频如果没解码就flush,会解码失败直接解码结束
isDecode=false;
mediaCodec.flush();
}
videoBufferInfo = new MediaCodec.BufferInfo();
long current = System.currentTimeMillis();
while (true) {
if (!threadRun) break;
if (!readEnd[0]) {
readEnd[0] = BaseMediaPlayer.putBufferToCoderSeek(mediaEditor.getVideoMime(),mediaExtractor,
mediaCodec, true, msc * AV_TIME_BASE);
isDecode=true;
}
int outputBufferIndex = mediaCodec.dequeueOutputBuffer(videoBufferInfo, BaseMediaPlayer.TIMEOUT_US);
if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
keyColorFormat = mediaCodec.getOutputFormat().getInteger(MediaFormat.KEY_COLOR_FORMAT);
} else if (outputBufferIndex >= 0) {
if ((videoBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {//最后一帧
currentPosition = mediaEditor.getVideoDuration();
mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
Log.e("========", "====current==end====="+msc);
break;
}else{
long currentSampleTime = videoBufferInfo.presentationTimeUs;
if (currentSampleTime >= msc * AV_TIME_BASE) {
Log.e("========", "========"+msc+"============current======="+ current);
cache = new VideoCache();
cache.strideHeiht = mediaEditor.getHeight();
cache.strideWidth = mediaEditor.getWidth();
cache.height = mediaEditor.getHeight();
cache.width = mediaEditor.getWidth();
cache.isNotSleep = true;
cache.setOutputBufferIndex(outputBufferIndex);
cache.setCurrentSampleTime(videoBufferInfo.presentationTimeUs);
lastSampleTime = videoBufferInfo.presentationTimeUs;
mediaCodec.releaseOutputBuffer(outputBufferIndex, true);
break;
} else {
mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
}
}
}
}
}
if (!playing||queue.size()>=20) {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
}
continue;
}
long re=System.currentTimeMillis();
sameSmc = -1;
if (!readEnd[0]) {
readEnd[0] = BaseMediaPlayer.
putBufferToCoder(mediaExtractor, mediaCodec);
isDecode=true;
}
isSeeking = false;
if (currentPosition>=mediaEditor.getVideoDuration()){//解码结束还有音频
syncPlay=false;
}
int outputBufferIndex = mediaCodec.dequeueOutputBuffer(videoBufferInfo,
BaseMediaPlayer.TIMEOUT_US);
ByteBuffer outputBuffer;
switch (outputBufferIndex) {
case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
break;
case MediaCodec.INFO_TRY_AGAIN_LATER:
if (threadRun) {
try {
// wait 10ms
Thread.sleep(10);
} catch (InterruptedException e) {
}
}
break;
case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
keyColorFormat = mediaCodec.getOutputFormat().getInteger(MediaFormat.KEY_COLOR_FORMAT);
break;
default:
if (outputBufferIndex >= 0) {
if (cache!=null&&cache.isNotSleep){
cache=null;
}
if (cache!=null){
synchronized (mSync) {
try {
mSync.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
if (!threadRun) break;
if ((videoBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {//结束
VideoCache videoCache = new VideoCache();
videoCache.setOutputBufferIndex(outputBufferIndex);
videoCache.setCurrentSampleTime(mediaEditor.getVideoDuration());
if (lastSampleTime > 0) {
long distant = (mediaEditor.getVideoDuration() - lastSampleTime) / AV_TIME_BASE;
videoCache.setDistant(distant);
}
lastSampleTime = mediaEditor.getVideoDuration();
syncPlay=false;
synchronized (objectQueue) {
queue.offer(videoCache);
}
mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
} else {
cache = new VideoCache();
cache.strideHeiht = mediaEditor.getHeight();
cache.strideWidth = mediaEditor.getWidth();
cache.height = mediaEditor.getHeight();
cache.width = mediaEditor.getWidth();
cache.render = true;
cache.setOutputBufferIndex(outputBufferIndex);
cache.setCurrentSampleTime(videoBufferInfo.presentationTimeUs);
if (lastSampleTime > 0) {
long distant = (videoBufferInfo.presentationTimeUs - lastSampleTime) / AV_TIME_BASE;
cache.setDistant(distant);
}
lastSampleTime = videoBufferInfo.presentationTimeUs;
mediaCodec.releaseOutputBuffer(outputBufferIndex, true);
}
}
break;
}
boolean end=false;
if (!isCompletion&¤tPosition == mediaEditor.getVideoDuration()) {
if (mediaEditor.isHasAudio()){
if (audioThread != null &&
audioThread.currentPosition == mediaEditor.getAudioDuration()){
end=true;
}
}else{
end=true;
}
if (end){
isCompletion = true;
if (completionListener != null) {
completionListener.onCompletion(MediaCodeImageReaderPlayer.this);
}
}
}
}
if (mediaCodec != null) {
mediaCodec.release();
}
if (mImageReader != null) {
mImageReader.close();
mImageReader = null;
stopImageReaderThread();
}
if (mediaExtractor != null) {
mediaExtractor.release();
mediaExtractor = null;
}
}
private void stopImageReaderThread() {
if (mImageReaderThread != null) {
mImageReaderThread.quitSafely();
try {
mImageReaderThread.join();
mImageReaderThread = null;
mImageReaderHandler = null;
} catch (Exception e) {
Log.e("=====", e.getMessage());
}
}
}
}