IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android音视频-MediaCodec硬解码获取所有帧数据 -> 正文阅读

[移动开发]Android音视频-MediaCodec硬解码获取所有帧数据

还是废了蛮多劲头,查了很多资料,终于能获取所有视频帧的数据了
依赖一些简单工具类,可以注释掉
还有一些不完善之处,比如如何指定解码宽高的,希望大神能指教
见代码

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;

/**
 * 使用Android提的mediaCodec硬解码解码视频获取所有帧,回调返回解码的到的视频帧数据,硬解码特点是速度更快
 * <p>
 * 视频帧数据就是Android提供的ImageReader,然后再通过ImageReader获取数据做相应的处理
 * <p>
 * 关于解码到的ImageReader里面数据的格式,指定的是YUV格式的一类,ImageFormat.YUV_420_888 == COLOR_FormatYUV420Flexible
 * 但是这个格式里面又分了很多的子格式,由于厂商实现不同等原因,处理是需要进行判断,然后处理
 * <p>
 * 如果想要yuv转bitmap的rgb,
 * 具体可以通过libyuv库提供的接口,或者Android自带的几种方法,RenderScript,YuvImage等
 * <p>
 * 解码过程 包括获取视频信息 是异步的
 * <p>
 * 应该是可以指定解码宽高的,但是目前没成功
 */
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class VideoFrameDecoder extends HandlerThread {
    public static final String TAG = "VideoFrameDecoder";


    // 控制选项
    /**
     * 控制取帧的间隔,为0表示所有帧
     */
    private long intervalMs = 0;//ms
    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;

    /**
     * 解码器解码运行在这个handle所在的线程
     */
    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 {
                // 设置fileName
                extractor.setDataSource(fileName);

                MediaFormat videoFormat = extractVideoInfo();
                if (videoFormat == null) {
                    decoderCallBack.onError(new Exception("无法获取视频信息"));
                    return;
                }
//todo
      /*   **上面提到了 MediaCodecList,这里简单说一下,使用 MediaCodecList 可以方便的列出当前设备支持的所有的编解码器
      ,创建 MediaCodec 的时候要选择当前格式支持的编解码器,也就是选择的编解码器需支持对应的 MediaFormat,
      每个编解码器都被包装成一个 MediaCodecInfo 对象,据此可以查看该编码器的一些特性,
      比如是否支持硬件加速、是软解还是硬解编解码器等,常用的简单如下**:
```java
                1// 是否软解
                2public boolean isSoftwareOnly ()
                3// 是Android平台提供(false)还是厂商提供(true)的编解码器
                4public boolean isVendor ()
                5// 是否支持硬件加速
                6public boolean isHardwareAccelerated ()
                7// 是编码器还是解码器
                8public boolean isEncoder ()
                9// 获取当前编解码器支持的合适
                10public String[] getSupportedTypes ()
                11// ...
```*/
                // 设置解码器需要的视频的参数
                // 实测发现这个宽高设置无效,网上找了一些资料没找到缩放相关的,似乎不支持缩放
                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));

                // 设置解码输出位置 surface 的参数
                int imageFormat = ImageFormat.YUV_420_888;
                imageReader = ImageReader.newInstance(
                        dstW, dstH, // 实测发现这个宽高设置无效,网上找了一些资料没找到缩放相关的,似乎不支持缩放
                        imageFormat,
                        1 // 解码到的surface缓存的帧数
                );

                // 配置好解码器,准备开始
                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();
            // processByExtractor(scale, decoderCallBack);
        });
    }

    /**
     * 每次解码一帧 然后等待消费位置消费完解码的那一帧之后, 回调解码器进行解码这样才不会丢帧
     */
    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 {
                // 这个循环会不断的解码数据往输出位置送,比如imageReader的surface,
                // 但是要注意的一定是,如果surface那边没有消费处理掉的帧,就会被新的解码数据覆盖,也就是说这一帧就丢失了
                if (outputDone) return;

                // 注意解码器一次输入的不一定是一帧的数据,且不会立即解码出来,所以要死循环等待一个完整的帧被解码出来
                boolean hasDecodeFrameFinish = false;
                while (!hasDecodeFrameFinish) {
                    if (requestStop) {
                        return;
                    }
                    if (!inputFinish) {
                        // 获取可用的输入缓冲区的索引
                        int inputBufferIndex = mediaCodec.dequeueInputBuffer(timeOutUs);
                        if (inputBufferIndex >= 0) {
                            ByteBuffer inputBuffer;
                            // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                            // 获取输入缓冲区
                            inputBuffer = mediaCodec.getInputBuffer(inputBufferIndex);
                            // } else {
                            //     inputBuffer = inputBuffers[inputBufferIndex];
                            // }
                            // 填充数据
                            // simpleData the sample size (or -1 if no more samples are available).
                            // 读取数据的size,<=0表示读完了
                            int sampleData = extractor.readSampleData(inputBuffer, 0);
                            if (sampleData > 0) {
                                long sampleTime = extractor.getSampleTime();
                                LogUtil.logTrack(String.format(Locale.CHINA, "解码到的时间 = %.4fs",
                                        sampleTime / 1000000f));
                                // 将填满数据的inputBuffer提交到编码队列
                                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++;
                                    }
//                                        extractor.advance();
                                }
                            } 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) {
                        //get data
                        // 获取已成功编解码的输出缓冲区的索引
                        // status就是当前解码的状态,可能正在解码中,可能解码完成了
                        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 {
                            //在这里判断,当前编码器的状态
                            // bufferInfo.flags 标记关键帧或者结束帧
                            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) {//离最后50ms的
                                            //输出最后一帧.强制输出最后一帧附近的帧的话,会比用metaRetiriever多一帧
                                            lastPresentationTimeUs = durationUs;
                                        } else {
                                            doRender = false;
                                        }
                                    } else {
                                        lastPresentationTimeUs = presentationTimeUs;
                                    }
                                    LogUtil.logTrack(
                                            "diff time in ms =" + diff / 1000);
                                }
                            }
                            //有数据了.因为会直接传递给Surface,所以说明都不做好了
                            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();
            }
        }
    }

    // 原始代码,保留,一次性解码所有帧,如果解码出来的帧的处理的位置的速度跟不上解码器,就会丢帧
 /*   private void processByExtractor(int scale, DecoderCallBack callBack) {
        try {
            MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
            long timeOutUs = 5 * 1000; // 微秒,不是毫秒
            boolean inputDone = false;
            boolean outputDone = false;
            ByteBuffer[] inputBuffers = null;
            // if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            //     inputBuffers = mediaCodec.getInputBuffers();
            // }
            //开始进行解码。
            int count = 1;
            LogUtil.logTrack("开始解码 \n 当前线程是: " + Thread.currentThread().getName());
            if (progressCallback != null) {
                progressCallback.addSeg("正在解码...");
            }
            // 这个循环会不断的解码数据往输出位置送,比如imageReader的surface,
            // 但是要注意的一定是,如果surface那边没有消费处理掉的帧,就会被新的解码数据覆盖,也就是说这一帧就丢失了
            while (!outputDone) {
                if (requestStop) {
                    return;
                }
                if (!inputDone) {
                    //feed data
                    // 获取可用的输入缓冲区的索引
                    int inputBufferIndex = mediaCodec.dequeueInputBuffer(timeOutUs);
                    if (inputBufferIndex >= 0) {
                        ByteBuffer inputBuffer;
                        // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                        // 获取输入缓冲区
                        inputBuffer = mediaCodec.getInputBuffer(inputBufferIndex);
                        // } else {
                        //     inputBuffer = inputBuffers[inputBufferIndex];
                        // }
                        // 填充数据
                        // simpleData the sample size (or -1 if no more samples are available).
                        // 读取数据的size,<=0表示读完了
                        int sampleData = extractor.readSampleData(inputBuffer, 0);
                        if (sampleData > 0) {
                            long sampleTime = extractor.getSampleTime();
                            LogUtil.logTrack(String.format(Locale.CHINA, "解码到的时间 = %.4fs",
                                    sampleTime / 1000000f));
                            // 将填满数据的inputBuffer提交到编码队列
                            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++;
                                }
//                                        extractor.advance();
                            }
                        } else {
                            // 读完了,给解码器设置结束标志
                            mediaCodec.queueInputBuffer(inputBufferIndex, 0, 0, 0L,
                                    MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                            inputDone = true;
                            LogUtil.logTrack("解码输入数据完成 \n 当前线程是: " + Thread.currentThread().getName());
                            if (progressCallback != null) {
                                progressCallback.onProgress(progressCallback.progress + 1 / 1000f);
                            }
                        }
                    }
                }
                if (!outputDone) {
                    //get data
                    // 获取已成功编解码的输出缓冲区的索引
                    // status就是当前解码的状态,可能正在解码中,可能解码完成了
                    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 {
                        //在这里判断,当前编码器的状态
                        // bufferInfo.flags 标记关键帧或者结束帧
                        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) {//离最后50ms的
                                        //输出最后一帧.强制输出最后一帧附近的帧的话,会比用metaRetiriever多一帧
                                        lastPresentationTimeUs = durationUs;
                                    } else {
                                        doRender = false;
                                    }
                                } else {
                                    lastPresentationTimeUs = presentationTimeUs;
                                }
                                LogUtil.logTrack(
                                        "diff time in ms =" + diff / 1000);
                            }
                        }
                        //有数据了.因为会直接传递给Surface,所以说明都不做好了
                        if (progressCallback != null) {
                            progressCallback.onProgress(progressCallback.progress + 1 / 1000f);
                        }
                        // 释放输出缓冲区,给到客户端调用者,直接送显就可以了
                        mediaCodec.releaseOutputBuffer(status, doRender);
                    }
                }
            }
        } catch (Exception e) {
            String message = "解码过程出错,已停止解码 " + e.getMessage();
            if (decoderCallBack != null) {
                decoderCallBack.onError(new Exception(message));
            }
            LogUtil.logTrack(message);
            e.printStackTrace();
        } finally {
            // 暂时不做处理,如果还在处理数据的过程中,
            // 释放或关闭会导致回调丢失
            // 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) {
            // 中端机器测试得,720 * 1280大小的视频,不加上其它处理,3-4ms解码出一帧yuv
            // maxImages–用户希望同时访问的最大图像数。这应该尽可能小,以限制内存使用。一旦用户获得maxImages图像,必须先发布其中一个图像,然后才能通过acquireLatestImage()或acquireNextImage()访问新图像。必须大于0。
            frameNumber++;
            LogUtil.logTrack("");
            LogUtil.logTrack("---------------------------------------------------------------------------------");
            LogUtil.logTrack("收到视频帧解码第 " + frameNumber +
                    " 帧回调" + Thread.currentThread().getName());
            LogUtil.logTrack("---------------------------------------------------------------------------------");
            LogUtil.logTrack("");
            /*  测试不处理是获
              Image img = null;
                try {
                    img = reader.acquireLatestImage();
                } catch (Exception e) {
                    LogUtil.logTrack("测试获取视频帧的image出错");
                } finally {
                    if (img != null) {
                        img.close();
                    }
                }*/
            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) {
        // TODO Auto-generated method stub
        //获取所有变量的值
        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 {

        /**
         * 注意!!!
         * 必须在调用线程完成处理,因为共享帧数据,需要一步一步走,
         * 如果切换线程顺序乱了会导致数据混乱
         * <p>
         * 也不能拷贝数据到其它线程,因为帧很多,数据量很大
         */
        void syncProcessFrame(ImageReader frame, int frameId, int rotation);

        void onFrameError(int frameId, Exception e);

        void onError(Exception e);

        void onInitVideoSuccess();

        void onDecodeFinish();
    }
}

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-05-05 11:29:58  更:2022-05-05 11:32:35 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 23:40:27-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码