H264对比H265

压缩效率

  • H.265
    压缩效率提高了约 50% 同等视觉质量 可以将文件压缩更小
  • H.264
    同等比特率 文件更大或者图像质量更低

算法

  • H.265
    更大的预测块(64*64 像素) 改进的运动补偿和变换
  • H.264
    较小的块(16*16 像素)

图像质量

  • H.265
    低比特率和高分辨率下可以更好的保证图像质量 减少压缩伪影
  • H.264
    相同比特率 会出现更多伪影和细节损失

计算复杂度

  • H.265
    更复杂的编码和解码过程 需要更高的计算资源和处理能力 编码较慢编码较快
  • H.264
    编码和解码复杂度较低 适合处理能力有限的设备

分辨率和帧率

  • H.265
    更高的分辨率(8K) 更高的帧率 适合高质量和高分辨率视频需求
  • H.264
    4K以下分辨率 可以支持高分辨率但性能效率低

兼容性

  • H.265
    旧设备或软件不被支持
  • H.264
    都兼容

如何解析?

H265

编码

try {
    // 创建解码器,指定要使用的解码格式
    MediaCodec codec = MediaCodec.createDecoderByType("video/hevc");
    MediaFormat format = MediaFormat.createVideoFormat("video/hevc", width, height);

   //描述平均位速率(以位/秒为单位)的键。 关联的值是一个整数
    mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height * 3/ 8);
   //描述视频格式的帧速率(以帧/秒为单位)的键。帧率,一般在15至30之内,太小容易造成视 频卡顿。
    mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
    //色彩格式,具体查看相关API,不同设备支持的色彩格式不尽相同
    mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, 
    MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);
    //关键帧间隔时间,单位是秒
    mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2);
    mediaFormat.setInteger(MediaFormat.KEY_BITRATE_MODE, 
    MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR);
    
    // 设置解码器的一些配置, 如需要的参数
    codec.configure(format, surface, null, 0);
    codec.start();
} catch (IOException e) {
    e.printStackTrace();
}
private void encoder(byte[] data){
        try {
            //拿到输入缓冲区,用于传送数据进行编码
            ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
            //拿到输出缓冲区,用于取到编码后的数据
            ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();
            int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);
            //当输入缓冲区有效时,就是>=0
            if (inputBufferIndex >= 0) {
                ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
                inputBuffer.clear();
                //往输入缓冲区写入数据
                inputBuffer.put(data);
                //五个参数,第一个是输入缓冲区的索引,第二个数据是输入缓冲区起始索引,第三个是放入的数据大小,第四个是时间戳,保证递增就是
                mediaCodec.queueInputBuffer(inputBufferIndex, 0, data.length, System.nanoTime() / 1000, 0);
            }
            MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); // 用于存储解码或编码过程中的数据包的相关信息(比如时间戳、数据大小等)
            //拿到输出缓冲区的索引
            int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
            while (outputBufferIndex >= 0) {
                ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
                byte[] outData = new byte[bufferInfo.size];
                outputBuffer.get(outData);


                // 将编码后的数据写入 MediaMuxer
                if (!muxerStarted) {
                    MediaFormat outputFormat = mediaCodec.getOutputFormat();
                    videoTrackIndex = mediaMuxer.addTrack(outputFormat);
                    mediaMuxer.start();
                    muxerStarted = true;
                }
                mediaMuxer.writeSampleData(videoTrackIndex, outputBuffer, bufferInfo); // MediaMuxer 允许你将音频和视频的编码数据合并为一个文件 比如MP4

                mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
                outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
            }
        } catch (Throwable t) {
            t.printStackTrace();
        }

    }

BufferInfo 主要字段

  • offset
    数据缓冲区的起始位置 通常是 0 表示从缓冲区的起始位置开始读取数据

  • size
    当前数据块的大小,单位是字节 它指示了数据缓冲区中有效数据的长度

  • presentationTimeUs
    当前数据的展示时间戳 单位是微秒(microseconds) 它是数据应该被显示(或者说播放)时的时间 通常用于音视频同步 这是一个非常重要的参数 尤其是在视频播放、视频文件封装时

  • flags
    用于指示数据块的标志 这个字段表示数据的特性 如是否为关键帧(BUFFER_FLAG_KEY_FRAME)等 它可以包含多个标志位 用来描述缓冲区中的数据

解码

mediaCodec = MediaCodec.createDecoderByType(TYPE_HEVC);
mediaFormat = MediaFormat.createVideoFormat(TYPE_HEVC, mOriginalWidth, mOriginalHeight);
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, framerate);
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
public void putData(byte[] data, long timestamp) {
        if (data == null || data.length == 0) {
            Log.e(TAG, "rtsp拉流无效的输入数据");
            return;
        }
        if (queue.size() >= MAX_QUEUE_SIZE) {
            queue.clear();
        }

        RtspDecoderData decoderData = new RtspDecoderData();
        decoderData.data = data;
        decoderData.timestamp = timestamp;
        queue.offer(decoderData);


    }

    private void tryToFeedDecoder(){
        try {
            if (!mAvailableBuffers.isEmpty() && !queue.isEmpty() && mediaCodec != null) {
                RtspDecoderData input = queue.poll();
                if (input != null) {
                    int bufferIndex = mAvailableBuffers.remove(0);
                    ByteBuffer decoderInputBuffer = mediaCodec.getInputBuffer(bufferIndex);
                    decoderInputBuffer.clear();
                    decoderInputBuffer.put(input.data, 0, input.data.length);
                    mediaCodec.queueInputBuffer(bufferIndex, 0, input.data.length, input.timestamp, 0);
                }

            }
        } catch (Exception exception) {
            Log.d(TAG, "tryToFeedDecoder: " + exception.getMessage());
        }

    }



    public void decode() {
        mediaCodec.setCallback(new MediaCodec.Callback() {
            @Override
            public void onInputBufferAvailable(@NonNull MediaCodec codec, int index) {
                if (mDebug) {
                    Log.d(TAG, "onInputBufferAvailable index: " + index);
                }
                mAvailableBuffers.add(index);
            }

            @Override
            public void onOutputBufferAvailable(@NonNull MediaCodec codec, int index, @NonNull MediaCodec.BufferInfo info) {
                if (mDebug) {
                    Log.d(TAG, "onOutputBufferAvailable index: " + index + " time: " + System.currentTimeMillis());
                }

                if (info.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
                    codec.releaseOutputBuffer(index, false);
                    return; // 处理流结束
                }

                if (info.size <= 0) {
                    Log.w(TAG, "检测到损坏的帧,跳过");
                    codec.releaseOutputBuffer(index, false);
                    return; // 跳过损坏的帧
                }

                ByteBuffer outputBuffer = codec.getOutputBuffer(index);
                if (outputBuffer == null) {
                    Log.e(TAG, "输出缓冲区为空");
                    codec.releaseOutputBuffer(index, false);
                    return;// 如果输出缓冲区无效,则跳过处理
                }
                mLastBuffer = outputBuffer;

                if (mFishEyeCorrectionTag) {
                    mResultBuffer.clear();
                    outputBuffer.position(0);
                    if (mIsCut) {
                        long before = System.currentTimeMillis();
                        if (mDebug) {
                            Log.d(TAG, "fisheyeCutNV12ToNv12ByCl start");
                        }
                        fishEyeCorrectionNative.fisheyeCutNV12ToNv12ByCl(outputBuffer, mResultBuffer, FishEyeCorrectionManager.getInstance().mPitch, 0);
                        if (mDebug) {
                            Log.d(TAG, "fisheyeCutNV12ToNv12ByCl end=" + (System.currentTimeMillis() - before));
                        }
                    } else {
                        long before = System.currentTimeMillis();
                        if (mDebug) {
                            Log.d(TAG, "fisheyeRemapNV12ToNv12ByCl start");
                        }
                        fishEyeCorrectionNative.fisheyeRemapNV12ToNv12ByCl(outputBuffer, mResultBuffer, FishEyeCorrectionManager.getInstance().mPitch, 0);
                        if (mDebug) {
                            Log.d(TAG, "fisheyeRemapNV12ToNv12ByCl end=" + (System.currentTimeMillis() - before));
                        }
                    }

                    mResultBuffer.get(resultData);


                    if (callBack != null) {
                        callBack.callBack(resultData, true);
                    }
                    mediaCodec.releaseOutputBuffer(index, false);
                } else {
                    outputBuffer.get(mYuvData);
                    if (callBack != null) {
                        callBack.callBack(mYuvData, false);
                    }
                    mediaCodec.releaseOutputBuffer(index, false);
                }


            }

            @Override
            public void onError(@NonNull MediaCodec codec, @NonNull MediaCodec.CodecException e) {
                Log.d(TAG, "onError e: " + e.getMessage());
                mediaCodec.reset();
                mAvailableBuffers.clear();

            }

            @Override
            public void onOutputFormatChanged(@NonNull MediaCodec codec, @NonNull MediaFormat format) {

            }
        });
        mediaCodec.configure(mediaFormat, null, null, 0);
        mediaCodec.start();

H264

// 初始化解码器
MediaCodec codec = MediaCodec.createDecoderByType("video/avc");

// 创建视频格式
MediaFormat format = MediaFormat.createVideoFormat("video/avc", width, height);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);

// 配置解码器,指定输出 Surface
Surface surface = surfaceView.getHolder().getSurface();
codec.configure(format, surface, null, 0);
codec.start();

// 送入数据
int inputBufferIndex = codec.dequeueInputBuffer(-1);
if (inputBufferIndex >= 0) {
    ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferIndex);
    inputBuffer.clear();
    inputBuffer.put(h264Data);  // h264Data 是你的 H.264 编码数据
    codec.queueInputBuffer(inputBufferIndex, 0, h264Data.length, 0, 0);
}

// 获取并渲染输出数据
int outputBufferIndex = codec.dequeueOutputBuffer(info, -1);
if (outputBufferIndex >= 0) {
    codec.releaseOutputBuffer(outputBufferIndex, true);  // 渲染到 Surface
}

// 清理资源
codec.stop();
codec.release();
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,125评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,293评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,054评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,077评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,096评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,062评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,988评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,817评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,266评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,486评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,646评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,375评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,974评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,621评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,642评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,538评论 2 352

推荐阅读更多精彩内容