Android音视频开发(四)——MediaCodec:解码视频,得到YUV值,一帧一帧加载到SD卡中保存。

        我们上一节了解了MediaExtractor、MediaMuxer、MediaFormat、MediaCodec.BufferInfo。重复的内容我就不再赘述了,假如有上面的四个的一些补充还是会写一下。接下来我们学习MediaCodec,本节篇幅会比较长,知识点较多,请耐心品味。

一、MediaCodec

        ​ MediaCodec类可用于访问低级媒体编解码器,即编码器/解码器组件。它是Android低级多媒体支持基础设施的一部分(通常与MediaExtractor, MediaSync, MediaMuxer, MediaCrypto, MediaDrm, Image, Surface,以及AudioTrack.)。

1.1数据类型

        编解码器处理三种数据:压缩数据原始音频数据原始视频数据。​ 所有这三种数据都可以使用ByteBuffers,但您应该使用Surface用于原始视频数据以提高编解码器性能。Surface使用原生视频缓冲区,而不将其映射或复制到ByteBuffers因此,它的效率更高。

        使用Surface时,通常无法访问原始视频数据,但可以使用ImageReader类来访问不安全的解码(原始)视频帧。这可能仍然比使用字节缓冲区更有效,因为一些本机缓冲区可以映射到直接的字节缓冲区。使用ByteBuffer模式时,可以使用Image类别和getInput/OutputImage(int)访问原始视频帧。

(1)压缩缓冲区

        压缩数据可以作为解码器的输入、编码器的输出,需要指定数据的格式,这样codec才知道如何处理这些压缩数据

  • MediaFormat.KEY_MIME格式类型。
  • 对于视频类型,通常是一个单独的压缩视频帧。
  • 对于音频数据,通常是一个单独的访问单元(一个编码的音频段通常包含由格式类型决定的几毫秒的音频),但是这个要求稍微宽松一些,因为一个buffer可能包含多个编码的音频访问单元。
  • 在这两种情况下,buffer都不会在任意字节边界上开始或结束,而是在帧/访问单元边界上开始或结束,除非它们被BUFFER_FLAG_PARTIAL_FRAME标记。

(2)原始音频缓冲区

        原始音频数据 — PCM音频数据帧,这是每个通道按通道顺序的一个样本。

(3)原始视频缓冲区

        在ByteBuffer模式下,视频buffer根据它们的MediaFormat.KEY_COLOR_FORMAT进行布局。可以从getCodecInfo(). MediaCodecInfo.getCapabilitiesForType.CodecCapability.colorFormats获取支持的颜色格式。视频编解码器可以支持三种颜色格式:

  • native raw video format: CodecCapabilities.COLOR_FormatSurface,可以与输入/输出的Surface一起使用。
  • flexible YUV buffers 例如CodecCapabilities.COLOR_FormatYUV420Flexible, 可以使用getInput/OutputImage(int)与输入/输出Surface一起使用,也可以在ByteBuffer模式下使用。
  • other, specific formats: 通常只支持ByteBuffer模式。有些颜色格式是厂商特有的,其他定义在CodecCapabilities。对于等价于flexible格式的颜色格式,可以使用getInput/OutputImage(int)。

        从Build.VERSION_CODES.LOLLIPOP_MR1.开始,所有视频编解码器都支持flexible的YUV 4:2:0 buffer。

1.2 MediaCodec API 

(1)MediaCodec创建:

  • createDecoderByType/createEncoderByType:根据特定MIME类型(如"video/avc")创建codec。然而,这不能用于注入特征,并且可能创建不能处理特定期望媒体格式的编解码器。
  • createByCodecName:知道组件的确切名称(如OMX.google.mp3.decoder)的时候,根据组件名创建codec。使用MediaCodecList可以获取组件的名称。

(2)MediaCodec配置与启动:

  • configure:配置解码器或者编码器。

四个参数:

  • MediaFormat format:输入数据的格式(解码器)或输出数据的所需格式(编码器)。传null等同于传递MediaFormat.MediaFormat作为空的MediaFormat。
  • Surface surface:指定Surface,用于解码器输出的渲染。如果编解码器不生成原始视频输出(例如,不是视频解码器)和/或想配置解码器输出ByteBuffer,则传null。
  • MediaCrypto crypto:指定一个crypto对象,用于对媒体数据进行安全解密。对于非安全的编解码器,传null。
  • int flags:当组件是编码器时,flags指定为常量CONFIGURE_FLAG_ENCODE。
  • start:成功配置组件后调用start。

(3)buffer处理的接口:

MediaCodec可以处理具体的视频流,主要有这几个方法:

  • getInputBuffers:获取需要编码数据的输入流队列,返回的是一个ByteBuffer数组 
  • getInputBuffer(index) : 获取InputBuffers数组index下标的ByteBuffer
  • queueInputBuffer:输入流入队列 
  • dequeueInputBuffer:从输入流队列中取数据进行编码操作 
  • getOutputBuffers:获取编解码之后的数据输出流队列,返回的是一个ByteBuffer数组 
  • getOutputBuffer(index) : 获取OutputBuffers数组index下标的ByteBuffer
  • dequeueOutputBuffer:从输出队列中取出编码操作之后的数据 
  • releaseOutputBuffer:处理完成,释放ByteBuffer数据 

(4)处理完之后的操作:

  • flush:清空的输入和输出端口。
  • stop:终止decode/encode会话
  • release:释放编解码器实例使用的资源。

二、MediaFormat 一些属性理解

        MediaFormat的格式被指定为键/值对。keys是字符串。values可以是整数、长整型、浮点型、字符串或ByteBuffer。(要素元数据被指定为  字符串/布尔  对。)

2.1 所有音频/视频格式通用的键,所有未标记为可选的键都是强制性的:

名字 值类型 描述
KEY_MIME String 格式的类型。
KEY_CODECS_STRING String 可选,媒体格式的RFC 6381编解码器字符串
KEY_MAX_INPUT_SIZE Integer 可选,输入数据缓冲区的最大大小
KEY_PIXEL_ASPECT_RATIO_WIDTH Integer 可选,像素长宽比宽度
KEY_PIXEL_ASPECT_RATIO_HEIGHT Integer 可选,像素纵横比高度
KEY_BIT_RATE Integer 仅编码器,以比特/秒为单位的所需比特率
KEY_DURATION Long 内容的持续时间(以微秒计)

2.2 视频格式有以下键:

名字 值类型 描述
KEY_WIDTH Integer
KEY_HEIGHT Integer
KEY_COLOR_FORMAT Integer 由用户为编码器设置,以解码器的输出格式可读
KEY_FRAME_RATE Integer or Float 要求用于编码器,可选用于解码器
KEY_CAPTURE_RATE Integer
KEY_I_FRAME_INTERVAL Integer or Float 仅编码器关键帧之间的时间间隔。添加了浮动支持android.os.Build.VERSION_CODES#N_MR1
KEY_INTRA_REFRESH_PERIOD Integer 仅编码器,可选
KEY_LATENCY Integer 仅编码器,可选
KEY_MAX_WIDTH Integer 仅解码器,可选,最大分辨率宽度
KEY_MAX_HEIGHT Integer 仅解码器,可选,最大分辨率高度
KEY_REPEAT_PREVIOUS_FRAME_AFTER Long 仅表面模式下的编码器,可选
KEY_PUSH_BLANK_BUFFERS_ON_STOP Integer 解码器仅渲染到表面,可选
KEY_TEMPORAL_LAYERING String 仅编码器可选的时间分层模式

2.3 音频格式有以下键:

名字 值类型 描述
KEY_CHANNEL_COUNT Integer
KEY_SAMPLE_RATE Integer
KEY_PCM_ENCODING Integer 可选择的
KEY_IS_ADTS Integer 可选,如果解码AAC音频内容,将此键设置为1表示每个音频帧都以ADTS标头为前缀。
KEY_AAC_PROFILE Integer 仅编码器可选,如果内容是AAC音频,则指定所需的配置文件。
KEY_AAC_SBR_MODE Integer 仅编码器,可选,如果内容是AAC音频,则指定所需的SBR模式。
KEY_AAC_DRC_TARGET_REFERENCE_LEVEL Integer 仅解码器可选,如果内容是AAC音频,则指定目标参考电平。
KEY_AAC_ENCODED_TARGET_LEVEL Integer 仅解码器可选,如果内容是AAC音频,则指定编码器使用的目标参考电平。
KEY_AAC_DRC_BOOST_FACTOR Integer 仅解码器,可选,如果内容是AAC音频,则指定DRC增强因子。
KEY_AAC_DRC_ATTENUATION_FACTOR Integer 仅解码器可选,如果内容是AAC音频,则指定DRC衰减系数。
KEY_AAC_DRC_HEAVY_COMPRESSION Integer 仅解码器可选,如果内容是AAC音频,则指定是否使用重度压缩。
KEY_AAC_MAX_OUTPUT_CHANNEL_COUNT Integer 仅解码器可选,如果内容是AAC音频,则指定解码器输出的最大通道数。
KEY_AAC_DRC_EFFECT_TYPE Integer 仅解码器可选,如果内容是AAC音频,则指定要使用的MPEG-D DRC效果类型。
KEY_AAC_DRC_OUTPUT_LOUDNESS Integer 仅解码器,可选,如果内容是AAC音频,则返回DRC输出响度。
KEY_AAC_DRC_ALBUM_MODE Integer 仅解码器可选,如果内容是AAC音频,则指定MPEG-D DRC专辑模式是否处于活动状态。
KEY_CHANNEL_MASK Integer 可选,音频通道分配的遮罩
KEY_ENCODER_DELAY Integer 可选,从解码的音频流的开始处要修剪的帧数。
KEY_ENCODER_PADDING Integer 可选,从解码的音频流的结尾开始修剪的帧数。
KEY_FLAC_COMPRESSION_LEVEL Integer 仅编码器,可选,如果内容是FLAC音频,则指定所需的压缩级别。
KEY_MPEGH_PROFILE_LEVEL_INDICATION Integer 仅解码器可选,如果内容是MPEG-H音频,则指定流的配置文件和级别。
KEY_MPEGH_COMPATIBLE_SETS ByteBuffer 仅解码器可选,如果内容是MPEG-H音频,则指定流的兼容集(配置文件和级别)。
KEY_MPEGH_REFERENCE_CHANNEL_LAYOUT Integer 仅解码器可选,如果内容是MPEG-H音频,则指定流的首选参考通道布局。

2.4 字幕格式有以下关键字:

线格式的类型。KEY_LANGUAGE线内容的语言。KEY_CAPTION_SERVICE_NUMBER(同Internationalorganizations)国际组织可选,隐藏字幕服务或频道号。

名字 值类型 描述
KEY_MIME String 格式的类型
KEY_LANGUAGE String 内容的语言
KEY_CAPTION_SERVICE_NUMBER int 可选,隐藏字幕服务或频道号

2.5 图像格式有以下键:

KEY_MIME String 格式的类型。
KEY_WIDTH Integer
KEY_HEIGHT Integer
KEY_COLOR_FORMAT Integer 由用户为编码器设置,以解码器的输出格式可读
KEY_TILE_WIDTH Integer 如果图像有网格,则需要
KEY_TILE_HEIGHT Integer 如果图像有网格,则需要
KEY_GRID_ROWS Integer 如果图像有网格,则需要
KEY_GRID_COLUMNS Integer 如果图像有网格,则需要

至于值,常用的也就那些,我就不提供了,自己去开发者文档查找。

三、Image 

与媒体源一起使用的单个完整图像缓冲区,例如 MediaCodec或 CameraDevice 。

该类允许通过一个或多个ByteBuffers高效地直接应用程序访问图像的像素数据。 每个缓冲区都封装在描述该平面中像素数据布局的Image.Plane中。 由于这种直接访问方式,与Bitmap类不同,图像不能直接用作UI资源。

由于图像通常由硬件组件直接生成或使用,因此它们是整个系统共享的有限资源,应在不再需要时立即关闭。

例如,当使用ImageReader类从各种媒体源读出图像时,一旦达到the maximum outstanding image count ,不关闭旧的图像对象将阻止新图像的可用性。 发生这种情况时,获取新图像的函数通常会抛出IllegalStateException 。

3.1 getPlanes() 方法

        获取该图像的像素平面阵列,平面的数量由图像的格式决定。这个需要了解ImageFormat的格式了,这个以后再了解。

3.2 ImageFormat.getBitsPerPixel()方法

        使用此函数可检索ImageFormat的每个像素的位数。

这个在此就点一下算了,之后在单独开一节来具体讲解。

四、demo实现

//解码操作,返回YUV加载的bitmap图片
public class ImageShowActivity extends Activity {
    private TextView tv_yun;
    //图片的个数
    private int imageNum = 0;
    private final String mVideoPath = Environment.getExternalStorageDirectory() + "/Pictures/music.mp4";
    private MediaExtractor extractor;//用于解封装
    private MediaFormat videoFormat;//保存视频轨道的媒体格式
    private MediaCodec mediaCodec;//解码视频轨道资源
    private int rotation;
    private long duration;

    //用于代表YUV的种类
    public static final int YUV420P = 0;
    public static final int YUV420SP = 1;
    public static final int NV21 = 2;

    //保存YUV数据的byte[]
    private byte[] bytes;

    private static int width;
    private static int height;

    //1.创建MediaExtractor和MediaCodec :  MediaExtractor负责解封装,MediaCodec负责解码视频轨道资源
    //2.解码获取图片,并进行转换:YUV_420_888-->NV21
    //3.YuvImage 加载nv21,转化成Bitmap用于显示。
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_image_show);

        tv_yun = findViewById(R.id.tv_image_yuv);

        GetVideo();
    }

}

//获取到视频轨道资源
    private void GetVideo(){
        extractor = new MediaExtractor();
        try {
            extractor.setDataSource(mVideoPath);
            //获取轨道个数
            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){
                throw new IllegalArgumentException("没有获取到视频的Format");
            }
            // 其中MediaFormat.KEY_COLOR_FORMAT 需要设置成COLOR_FormatYUV420Flexible
            // MediaCodec的所有硬件解码都支持这种格式
            videoFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,COLOR_FormatYUV420Flexible);
            videoFormat.setInteger(MediaFormat.KEY_WIDTH,videoFormat.getInteger(MediaFormat.KEY_WIDTH));
            videoFormat.setInteger(MediaFormat.KEY_HEIGHT,videoFormat.getInteger(MediaFormat.KEY_HEIGHT));
            if(videoFormat.containsKey(MediaFormat.KEY_ROTATION)){
                //获取旋转多少的值
                rotation = videoFormat.getInteger(MediaFormat.KEY_ROTATION);
            }
            //内容持续时间
            duration = videoFormat.getLong(MediaFormat.KEY_DURATION);
            //MediaCodec创建:createDecoderByType/createEncoderByType:根据特定MIME类型(如"video/avc")创建codec。
            //createByCodecName:知道组件的确切名称(如OMX.google.mp3.decoder)的时候,根据组件名创建codec。使用MediaCodecList可以获取组件的名称。
            mediaCodec = MediaCodec.createDecoderByType(videoFormat.getString(MediaFormat.KEY_MIME));
            //configure:配置解码器或者编码器。
            // 参数1:MediaFormat ,参数2:Surface ,参数3:MediaCrypto ,参数4:flags
            mediaCodec.configure(videoFormat,null,null,0);
            mediaCodec.start();
            //开始解码
            processByExtractor();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
  • 描述视频格式内容的颜色格式的关键字,需要在android.media.MediaCodecInfo.CodecCapabilities中声明。
  • 通过MediaFormat,获取了视频信息,duration 和rotation 是比较重要的。duration /1000/1000可以判断我们需要取几帧,1s一帧,rotation判断视频旋转角度,后面生成bitmap需要用到。
    //开始解码,获取帧序列
    private void processByExtractor() {
        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
        long timeOut = 5* 1000;//5ms
        boolean inputDone = false;
        boolean outputDone = false;
        ByteBuffer[] inputBuffers = null;
//        if(Build.VERSION.SDK_INT< Build.VERSION_CODES.LOLLIPOP){
//            //开始喂数据
//            inputBuffers = mediaCodec.getInputBuffers();
//        }
        inputBuffers = mediaCodec.getInputBuffers();
        //开始解码
        int count = 0;
        while (!outputDone){
            if(!inputDone){
                //喂数据
                //如果是要获取所有帧序列,则不需要使用seekTo方法。
                //extractor.seekTo(count * intervalMs * 1000,MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
                int inputBufferIndex = mediaCodec.dequeueInputBuffer(timeOut);
                if(inputBufferIndex >= 0){
                    ByteBuffer inputBuffer;
                    if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.LOLLIPOP){
                        inputBuffer = mediaCodec.getInputBuffer(inputBufferIndex);
                    }else {
                        inputBuffer = inputBuffers[inputBufferIndex];
                    }
                    int sampleDate = extractor.readSampleData(inputBuffer,0);

                    if(sampleDate > 0 && count * 1000 <= duration){
                        long sampleTime = extractor.getSampleTime();
                        int sampleFlags = extractor.getSampleFlags();

                        mediaCodec.queueInputBuffer(inputBufferIndex,0,sampleDate,sampleTime,0);
                        extractor.advance();
                        count++;
                    }else {
                        //小于0,就说明读完了
                        mediaCodec.queueInputBuffer(inputBufferIndex,0,0,0,MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                        inputDone = true;
                    }
                }
            }
            if(!outputDone){
                //获取数据
                int status = mediaCodec.dequeueOutputBuffer(bufferInfo,timeOut);
                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){
                        outputDone = true;
                    }
                    boolean doRender = (bufferInfo.size !=0);
                    //获取图片并保存,getOutputImage格式是YUV_420_888
                    Image image = mediaCodec.getOutputImage(status);
                    mediaCodec.getOutputBuffer(status);
                    System.out.println("成功获取到图片"+"SSSSSSSSSSSSSSSSSSSSSSS");
                    imageNum++;
                    //dateFromImage(image);
                    //使用新方法来获取yuv数据
                    bytes = getBytesFromImageAsType(image,2);

                    //根据yuv数据获取Bitmap
                    Bitmap bitmap = getBitmapFromYUV(bytes,width,height,rotation);
                    //保存图片
                    if(bitmap != null){
                        //显示图片
                        String businesslogofile=Environment.getExternalStorageDirectory()+"/Pictures/logo"+imageNum+".png";
                        File file = new File(businesslogofile);
                        try {
                            bitmap.compress(Bitmap.CompressFormat.JPEG,100,new FileOutputStream(file));
                        } catch (FileNotFoundException e) {
                            e.printStackTrace();
                        }
                        System.out.println("图片导入成功");
                    }

                    mediaCodec.releaseOutputBuffer(status,doRender);
                    //这里先尝试获取第一张图片
                    //break;

                }
            }
        }

    }

细将一下MediaCodec的四个方法:

1. dequeueInputBuffer:

public final int dequeueInputBuffer(long timeoutUs)
  • 返回用于填充有效数据的输入buffer的索引,如果当前没有可用的buffer,则返回-1
  • long timeoutUs等待可用的输入buffer的时间。
    • 如果timeoutUs == 0,则立即返回。
    • 如果timeoutUs < 0,则无限期等待可用的输入buffer。
    • 如果timeoutUs > 0,则等待“timeoutUs”微秒。

2. queueInputBuffer:在指定索引处填充输入buffer后,使用queueInputBuffer将buffer提交给组件。

许多codec要求实际压缩的数据流之前必须有“特定于codec的数据”,即用于初始化codec的设置数据,如

  • AVC视频中的PPS/SPS。
  • vorbis音频中的code tables。
public native final void queueInputBuffer(
    int index,
    int offset, int size, long presentationTimeUs, int flags)
  • int index:以前调用dequeueInputBuffer(long)返回的输入buffer的索引。
  • int offset:数据开始时输入buffer中的字节偏移量。
  • int size:有效输入数据的字节数。
  • long presentationTimeUs:此buffer的PTS(以微秒为单位)。
  • int flags:一个由BUFFER_FLAG_CODEC_CONFIG和BUFFER_FLAG_END_OF_STREAM标志组成的位掩码。虽然没有被禁止,但是大多数codec并不对输入buffer使用BUFFER_FLAG_KEY_FRAME标志。
    • BUFFER_FLAG_END_OF_STREAM:用于指示这是输入数据的最后一部分。
    • BUFFER_FLAG_CODEC_CONFIG:通过指定这个标志,可以在start()或flush()之后直接提交特定于codec的数据buffer。但是,如果您使用包含这些密钥的媒体格式配置编解码器,它们将在启动后由MediaCodec直接自动提交。因此,不建议使用BUFFER_FLAG_CODEC_CONFIG标志,只建议高级用户使用。

3. dequeueOutputBuffer:从MediaCodec获取输出buffer。

    public final int dequeueOutputBuffer(
            @NonNull BufferInfo info, long timeoutUs) 
  • 返回值:已成功解码的输出buffer的索引或INFO_*常量之一(INFO_TRY_AGAIN_LATER, INFO_OUTPUT_FORMAT_CHANGED 或 INFO_OUTPUT_BUFFERS_CHANGED)。
    • 返回INFO_TRY_AGAIN_LATER而timeoutUs指定为了非负值,表示超时了。
    • 返回INFO_OUTPUT_FORMAT_CHANGED表示输出格式已更改,后续数据将遵循新格式。
  • BufferInfo info:输出buffer的metadata。
  • long timeoutUs:含义同dequeueInputBuffer中的timeoutUs参数。

4. releaseOutputBuffer:使用此方法将输出buffer返回给codec或将其渲染在输出surface。

public void releaseOutputBuffer (int index, boolean render)
  • boolean render:如果在配置codec时指定了一个有效的surface,则传递true会将此输出buffer在surface上渲染。一旦不再使用buffer,该surface将把buffer释放回codec。

    //根据image获取yuv值-------------------NEW
    public static byte[] getBytesFromImageAsType(Image image, int type){
        try {
            //获取源数据,如果是YUV格式的数据planes.length = 3
            //plane[i]里面的实际数据可能存在byte[].length <= capacity (缓冲区总大小)
            final Image.Plane[] planes = image.getPlanes();

            //数据有效宽度,一般的,图片width <= rowStride,这也是导致byte[].length <= capacity的原因
            // 所以我们只取width部分
            width = image.getWidth();
            height = image.getHeight();

            //此处用来装填最终的YUV数据,需要1.5倍的图片大小,因为Y U V 比例为 4:1:1 (这里是YUV_420_888)
            byte[] yuvBytes = new byte[width * height * ImageFormat.getBitsPerPixel(ImageFormat.YUV_420_888) / 8];
            //目标数组的装填到的位置
            int dstIndex = 0;

            //临时存储uv数据的
            byte uBytes[] = new byte[width * height / 4];
            byte vBytes[] = new byte[width * height / 4];
            int uIndex = 0;
            int vIndex = 0;

            int pixelsStride, rowStride;
            for (int i = 0; i < planes.length; i++) {
                pixelsStride = planes[i].getPixelStride();
                rowStride = planes[i].getRowStride();

                ByteBuffer buffer = planes[i].getBuffer();

                //如果pixelsStride==2,一般的Y的buffer长度=640*480,UV的长度=640*480/2-1
                //源数据的索引,y的数据是byte中连续的,u的数据是v向左移以为生成的,两者都是偶数位为有效数据
                byte[] bytes = new byte[buffer.capacity()];
                buffer.get(bytes);

                int srcIndex = 0;
                if (i == 0) {
                    //直接取出来所有Y的有效区域,也可以存储成一个临时的bytes,到下一步再copy
                    for (int j = 0; j < height; j++) {
                        System.arraycopy(bytes, srcIndex, yuvBytes, dstIndex, width);
                        srcIndex += rowStride;
                        dstIndex += width;
                    }
                } else if (i == 1) {
                    //根据pixelsStride取相应的数据
                    for (int j = 0; j < height / 2; j++) {
                        for (int k = 0; k < width / 2; k++) {
                            uBytes[uIndex++] = bytes[srcIndex];
                            srcIndex += pixelsStride;
                        }
                        if (pixelsStride == 2) {
                            srcIndex += rowStride - width;
                        } else if (pixelsStride == 1) {
                            srcIndex += rowStride - width / 2;
                        }
                    }
                } else if (i == 2) {
                    //根据pixelsStride取相应的数据
                    for (int j = 0; j < height / 2; j++) {
                        for (int k = 0; k < width / 2; k++) {
                            vBytes[vIndex++] = bytes[srcIndex];
                            srcIndex += pixelsStride;
                        }
                        if (pixelsStride == 2) {
                            srcIndex += rowStride - width;
                        } else if (pixelsStride == 1) {
                            srcIndex += rowStride - width / 2;
                        }
                    }
                }
            }
            //   image.close();
            //根据要求的结果类型进行填充
            switch (type) {
                case YUV420P:
                    System.arraycopy(uBytes, 0, yuvBytes, dstIndex, uBytes.length);
                    System.arraycopy(vBytes, 0, yuvBytes, dstIndex + uBytes.length, vBytes.length);
                    break;
                case YUV420SP:
                    for (int i = 0; i < vBytes.length; i++) {
                        yuvBytes[dstIndex++] = uBytes[i];
                        yuvBytes[dstIndex++] = vBytes[i];
                    }
                    break;
                case NV21:
                    for (int i = 0; i < vBytes.length; i++) {
                        yuvBytes[dstIndex++] = vBytes[i];
                        yuvBytes[dstIndex++] = uBytes[i];
                    }
                    break;
            }
            return yuvBytes;
        } catch (final Exception e) {
            if (image != null) {
                image.close();
            }
        }
        return null;
    }
    private Bitmap getBitmapFromYUV(byte[] date, int width, int height, int rotation) {
        //使用YuvImage---》NV21
        YuvImage yuvImage = new YuvImage(date, ImageFormat.NV21,width,height,null);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        yuvImage.compressToJpeg(new Rect(0,0,width,height),20,baos);
        byte[] jdate =baos.toByteArray();
        BitmapFactory.Options bitmapFatoryOptions = new BitmapFactory.Options();
        bitmapFatoryOptions.inPreferredConfig = Bitmap.Config.RGB_565;
        bitmapFatoryOptions.inSampleSize = 4;
        if(rotation == 0){
            Bitmap bmp = BitmapFactory.decodeByteArray(jdate,0,jdate.length,bitmapFatoryOptions);
            return bmp;
        }else {
            Matrix m = new Matrix();
            m.postRotate(rotation);
            Bitmap bmp = BitmapFactory.decodeByteArray(jdate,0,jdate.length,bitmapFatoryOptions);
            Bitmap bml = Bitmap.createBitmap(bmp,0,0,bmp.getWidth(),bmp.getHeight(),m,true);
            return bml;
        }
    }

这一节就是对本系列的(一)(二)基本知识的利用了。这个有机会再开一个小节来讲。

猜你喜欢

转载自blog.csdn.net/indeedes/article/details/124932758