当前位置: 首页 > news >正文

音视频开发—MediaCodec 解码H264/H265码流视频

使用MediaCodec目的

MediaCodec是Android底层多媒体框架的一部分,通常与MediaExtractor、MediaMuxer、AudioTrack结合使用,可以编码H264、H265、AAC、3gp等常见的音视频格式

MediaCodec工作原理是处理输入数据以产生输出数据

MediaCodec工作流程

MediaCodec的数据流分为input和output流,并通过异步的方式处理两路数据流,直到手动释放output缓冲区,MediaCodec才将数据处理完毕

  • input流:客户端输入待解码或者待编码的数据
  • output流:客户端输出的已解码或者已编码的数据

MediaCodec API说明

  • getInputBuffers:获取需要输入流队列,返回ByteBuffer数组
  • queueInputBuffer:输入流入队
  • dequeueInputBuffer: 从输入流队列中取数据进行编码操作
  • getOutputBuffers:获取已经编解码之后的数据输出流队列,返回ByteBuffer数组
  • dequeueOutputBuffer:从输出队列中取出已经编码操作之后的数据
  • releaseOutputBuffer: 处理完成,释放output缓冲区

基本流程

  • MediaCodec的基本使用遵循上图所示,它的生命周期如下所示:
  • Stoped:创建好MediaCodec,进行配置,或者出现错误
  • Uninitialized: 当创建了一个MediaCodec对象,此时MediaCodec处于Uninitialized,在任何状态调用reset()方法使MediaCodec返回到Uninitialized状态
  • Configured: 使用configure(…)方法对MediaCodec进行配置转为Configured状态
  • Error: 出现错误
  • Executing:可以在Executing状态的任何时候通过调用flush()方法返回到Flushed状态
  • Flushed:调用start()方法后MediaCodec立即进入Flushed状态
  • Running:调用dequeueInputBuffer后,MediaCodec就转入Running状态
  • End-of-Stream:编解码结束后,MediaCodec将转入End-of-Stream子状态
  • Released:当使用完MediaCodec后,必须调用release()方法释放其资源

基本使用

//解码器
val mVideoDecoder = MediaCodec.createDecoderByType("video/avc")
//编码器
val mVideoEncoder = MediaCodec.createEncoderByType("video/avc")

MediaCodec 解码H264/H265

使用MediaCodec 解码H264/H265码流视频,那必须谈下MediaCodec这个神器。附官网数据流程图如下:

input:ByteBuffer输入方;

output:ByteBuffer输出方;

  • 使用者从MediaCodec请求一个空的输入buffer(ByteBuffer),填充满数据后将它传递给MediaCodec处理。
  • MediaCodec处理完这些数据并将处理结果输出至一个空的输出buffer(ByteBuffer)中。
  • 使用者从MediaCodec获取输出buffer的数据,消耗掉里面的数据,使用完输出buffer的数据之后,将其释放回编解码。

H264码流解码示例代码如下(基本都做了注释)

package com.zqfdev.h264decodedemo;
​
import android.media.MediaCodec;
import android.media.MediaFormat;
import android.util.Log;
import android.view.Surface;
​
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
​
/*** @author zhangqingfa* @createDate 2020/12/10 11:39* @description 解码H264播放*/
public class H264DeCodePlay {
​private static final String TAG = "zqf-dev";//视频路径private String videoPath;//使用android MediaCodec解码private MediaCodec mediaCodec;private Surface surface;
​H264DeCodePlay(String videoPath, Surface surface) {this.videoPath = videoPath;this.surface = surface;initMediaCodec();}
​private void initMediaCodec() {try {Log.e(TAG, "videoPath " + videoPath);//创建解码器 H264的Type为  AACmediaCodec = MediaCodec.createDecoderByType("video/avc");//创建配置MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", 540, 960);//设置解码预期的帧速率【以帧/秒为单位的视频格式的帧速率的键】mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);//配置绑定mediaFormat和surfacemediaCodec.configure(mediaFormat, surface, null, 0);} catch (IOException e) {e.printStackTrace();//创建解码失败Log.e(TAG, "创建解码失败");}}
​/*** 解码播放*/void decodePlay() {mediaCodec.start();new Thread(new MyRun()).start();}
​private class MyRun implements Runnable {
​@Overridepublic void run() {try {//1、IO流方式读取h264文件【太大的视频分批加载】byte[] bytes = null;bytes = getBytes(videoPath);Log.e(TAG, "bytes size " + bytes.length);//2、拿到 mediaCodec 所有队列buffer[]ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();//开始位置int startIndex = 0;//h264总字节数int totalSize = bytes.length;//3、解析while (true) {//判断是否符合if (totalSize == 0 || startIndex >= totalSize) {break;}//寻找索引int nextFrameStart = findByFrame(bytes, startIndex + 1, totalSize);if (nextFrameStart == -1) break;MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();// 查询10000毫秒后,如果dSP芯片的buffer全部被占用,返回-1;存在则大于0int inIndex = mediaCodec.dequeueInputBuffer(10000);if (inIndex >= 0) {//根据返回的index拿到可以用的bufferByteBuffer byteBuffer = inputBuffers[inIndex];//清空缓存byteBuffer.clear();//开始为buffer填充数据byteBuffer.put(bytes, startIndex, nextFrameStart - startIndex);//填充数据后通知mediacodec查询inIndex索引的这个buffer,mediaCodec.queueInputBuffer(inIndex, 0, nextFrameStart - startIndex, 0, 0);//为下一帧做准备,下一帧首就是前一帧的尾。startIndex = nextFrameStart;} else {//等待查询空的buffercontinue;}//mediaCodec 查询 "mediaCodec的输出方队列"得到索引int outIndex = mediaCodec.dequeueOutputBuffer(info, 10000);Log.e(TAG, "outIndex " + outIndex);if (outIndex >= 0) {try {//暂时以休眠线程方式放慢播放速度Thread.sleep(33);} catch (InterruptedException e) {e.printStackTrace();}//如果surface绑定了,则直接输入到surface渲染并释放mediaCodec.releaseOutputBuffer(outIndex, true);} else {Log.e(TAG, "没有解码成功");}}} catch (IOException e) {e.printStackTrace();}}}
​
​//读取一帧数据private int findByFrame(byte[] bytes, int start, int totalSize) {for (int i = start; i < totalSize - 4; i++) {//对output.h264文件分析 可通过分隔符 0x00000001 读取真正的数据if (bytes[i] == 0x00 && bytes[i + 1] == 0x00 && bytes[i + 2] == 0x00 && bytes[i + 3] == 0x01) {return i;}}return -1;}private byte[] getBytes(String videoPath) throws IOException {InputStream is = new DataInputStream(new FileInputStream(new File(videoPath)));int len;int size = 1024;byte[] buf;ByteArrayOutputStream bos = new ByteArrayOutputStream();buf = new byte[size];while ((len = is.read(buf, 0, size)) != -1)bos.write(buf, 0, len);buf = bos.toByteArray();return buf;}
}

H265示例代码如下

package com.zqfdev.h264decodedemo;
​
import android.media.MediaCodec;
import android.media.MediaFormat;
import android.util.Log;
import android.view.Surface;
​
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
​
/*** @author zhangqingfa* @createDate 2020/12/10 11:39* @description 解码H264播放*/
public class H265DeCodePlay {
​private static final String TAG = "zqf-dev";//视频路径private String videoPath;//使用android MediaCodec解码private MediaCodec mediaCodec;private Surface surface;
​H265DeCodePlay(String videoPath, Surface surface) {this.videoPath = videoPath;this.surface = surface;initMediaCodec();}
​private void initMediaCodec() {try {Log.e(TAG, "videoPath " + videoPath);//创建解码器 H264的Type为  AACmediaCodec = MediaCodec.createDecoderByType("video/hevc");//创建配置MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/hevc", 368, 384);//设置解码预期的帧速率【以帧/秒为单位的视频格式的帧速率的键】mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);//配置绑定mediaFormat和surfacemediaCodec.configure(mediaFormat, surface, null, 0);} catch (IOException e) {e.printStackTrace();//创建解码失败Log.e(TAG, "创建解码失败");}}
​/*** 解码播放*/void decodePlay() {mediaCodec.start();new Thread(new MyRun()).start();}
​private class MyRun implements Runnable {
​@Overridepublic void run() {try {//1、IO流方式读取h264文件【太大的视频分批加载】byte[] bytes = null;bytes = getBytes(videoPath);Log.e(TAG, "bytes size " + bytes.length);//2、拿到 mediaCodec 所有队列buffer[]ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();//开始位置int startIndex = 0;//h264总字节数int totalSize = bytes.length;//3、解析while (true) {//判断是否符合if (totalSize == 0 || startIndex >= totalSize) {break;}//寻找索引int nextFrameStart = findByFrame(bytes, startIndex + 1, totalSize);if (nextFrameStart == -1) break;Log.e(TAG, "nextFrameStart " + nextFrameStart);MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();// 查询10000毫秒后,如果dSP芯片的buffer全部被占用,返回-1;存在则大于0int inIndex = mediaCodec.dequeueInputBuffer(10000);if (inIndex >= 0) {//根据返回的index拿到可以用的bufferByteBuffer byteBuffer = inputBuffers[inIndex];//清空byteBuffer缓存byteBuffer.clear();//开始为buffer填充数据byteBuffer.put(bytes, startIndex, nextFrameStart - startIndex);//填充数据后通知mediacodec查询inIndex索引的这个buffer,mediaCodec.queueInputBuffer(inIndex, 0, nextFrameStart - startIndex, 0, 0);//为下一帧做准备,下一帧首就是前一帧的尾。startIndex = nextFrameStart;} else {//等待查询空的buffercontinue;}//mediaCodec 查询 "mediaCodec的输出方队列"得到索引int outIndex = mediaCodec.dequeueOutputBuffer(info, 10000);Log.e(TAG, "outIndex " + outIndex);if (outIndex >= 0) {try {//暂时以休眠线程方式放慢播放速度Thread.sleep(33);} catch (InterruptedException e) {e.printStackTrace();}//如果surface绑定了,则直接输入到surface渲染并释放mediaCodec.releaseOutputBuffer(outIndex, true);} else {Log.e(TAG, "没有解码成功");}}} catch (IOException e) {e.printStackTrace();}}}
​
​//读取一帧数据private int findByFrame(byte[] bytes, int start, int totalSize) {for (int i = start; i < totalSize - 4; i++) {//对output.h264文件分析 可通过分隔符 0x00000001 读取真正的数据if (bytes[i] == 0x00 && bytes[i + 1] == 0x00 && bytes[i + 2] == 0x00 && bytes[i + 3] == 0x01) {return i;}}return -1;}private byte[] getBytes(String videoPath) throws IOException {InputStream is = new DataInputStream(new FileInputStream(new File(videoPath)));int len;int size = 1024;byte[] buf;ByteArrayOutputStream bos = new ByteArrayOutputStream();buf = new byte[size];while ((len = is.read(buf, 0, size)) != -1)bos.write(buf, 0, len);buf = bos.toByteArray();return buf;}
}

MainActivity代码如下

package com.zqfdev.h264decodedemo;
​
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
​
import java.io.File;
​
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
​
public class MainActivity extends AppCompatActivity {private String[] permiss = {"android.permission.WRITE_EXTERNAL_STORAGE", "android.permission.READ_EXTERNAL_STORAGE"};private H264DeCodePlay h264DeCodePlay;
//    private H265DeCodePlay h265DeCodePlay;private String videoPath;
​@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);checkPermiss();initView();}private void checkPermiss() {int code = ActivityCompat.checkSelfPermission(this, permiss[0]);if (code != PackageManager.PERMISSION_GRANTED) {// 没有写的权限,去申请写的权限ActivityCompat.requestPermissions(this, permiss, 11);}}private void initView() {File dir = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);if (!dir.exists()) dir.mkdirs();final File file = new File(dir, "output.h264");
//        final File file = new File(dir, "output.h265");if (!file.exists()) {Log.e("Tag", "文件不存在");return;}videoPath = file.getAbsolutePath();final SurfaceView surface = findViewById(R.id.surface);final SurfaceHolder holder = surface.getHolder();holder.addCallback(new SurfaceHolder.Callback() {@Overridepublic void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {h264DeCodePlay = new H264DeCodePlay(videoPath, holder.getSurface());h264DeCodePlay.decodePlay();
//                h265DeCodePlay = new H265DeCodePlay(videoPath, holder.getSurface());
//                h265DeCodePlay.decodePlay();}
​@Overridepublic void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int i, int i1, int i2) {}@Overridepublic void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {}});}
}

测试的H264 / H265码流视频通过FFmpeg抽取可得到。

命令行:

ffmpeg -i 源视频.mp4 -codec copy -bsf: h264_mp4toannexb -f h264 输出视频.h264

最后效果如下:

相关文章:

音视频开发—MediaCodec 解码H264/H265码流视频

使用MediaCodec目的 MediaCodec是Android底层多媒体框架的一部分&#xff0c;通常与MediaExtractor、MediaMuxer、AudioTrack结合使用&#xff0c;可以编码H264、H265、AAC、3gp等常见的音视频格式 MediaCodec工作原理是处理输入数据以产生输出数据 MediaCodec工作流程 Med…...

CVPR 2023|淘宝视频质量评价算法被顶会收录

近日&#xff0c;阿里巴巴大淘宝技术题为《MD-VQA: Multi-Dimensional Quality Assessment for UGC Live Videos》—— 适用于无参考视频质量评价的最新研究成果被计算机视觉领域顶级会议IEEE/CVF Computer Vision and Pattern Recognition Conference 2023&#xff08;CVPR 20…...

【C++学习】继承

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《C学习》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; C是面向对象的编程语言&#xff0c;它有很多的特性&#xff0c;但是最重要的就是封装&#xff0c;继承…...

【03173】2020年8月高等教育自学考试-软件开发工具

一、单项选择题&#xff1a;1. 区别于一般软件&#xff0c;对软件开发工具而言&#xff0c;下列各项最重要的性能是 A. 效率 B. 响应速度C. 资源消耗 D. 使用方便2. 在软件开发过程的信息需求中&#xff0c;属于跨开发周期的信息是A. 有关系统环境的需求信息 B. 有关软件设计的…...

Java中的String类

String类1.String类1.1 特性1.2 面试题1.3 常用方法1.4 String与其他类型之间的转换2. StringBuilder类、StringBuffer类&#xff1a;可变字符序列1.String类 1.1 特性 String类为final类&#xff0c;不可被继承&#xff0c;代表不可变的字符序列&#xff1b; 实现了Serializ…...

【java】笔试强训Day3【在字符串中找出连续最长的数字串与数组中出现次数超过一半的数字】

目录 ⛳选择题 1.以下代码运行输出的是 2.以下程序的输出结果为 3.下面关于构造方法的说法不正确的是 ( ) 4.在异常处理中&#xff0c;以下描述不正确的有&#xff08; &#xff09; 5.下列描述中&#xff0c;错误的是&#xff08; &#xff09; 6.…...

一文7个步骤从0到1教你搭建Selenium 自动化测试环境

【导语】Selenium是一个用于Web应用程序测试的工具。Selenium测试直接运行在浏览器中&#xff0c;就像真正的用户在操作一样。支持自动录制动作和自动生成 .Net、Java、Perl等不同语言的测试脚本。本文详细介绍了搭建自动化测试环境所需的工具&#xff0c;让你学习自动化测试不…...

Oracle目录应急清理

Oracle目录应急清理清理错误位置的归档日志清理30天前的监听告警日志清理监听日志清理30天以前的trace文件清理30天以前的审计日志清理错误位置的归档日志 检查$ORACLE_HOME/dbs下是否有归档文件&#xff1a; ls $ORACLE_HOME/dbs/arch* | wc -l检查和修改归档位置&#xff1…...

使用 OBS 进行区域录制

1. OBS 与区域录屏 实际上 OBS 的使用场景可谓是与区域录屏格格不入的。 虽然我们依旧有一些办法在 OBS 中达到区域录屏的目的&#xff0c;但其操作实在过于繁琐&#xff0c;还不如直接使用 QQ 或者 Windows 最新的自带截屏录屏来进行区域录屏来的方便实在。 但若非常强烈的…...

aws eks 配置授权额外的用户和角色访问集群

参考资料 https://github.com/kubernetes-sigs/aws-iam-authenticator#full-configuration-formathttps://docs.amazonaws.cn/zh_cn/eks/latest/userguide/add-user-role.html 众所周知&#xff0c;aws eks使用 Authenticator 或者 aws 命令来进行账户级别的用户和角色的授权…...

MagicalCoder可视化开发平台:轻松搭建业务系统,为企业创造更多价值

让软件应用开发变得轻松起来&#xff0c;一起探索MagicalCoder可视化开发工具的魔力&#xff01;你是否为编程世界的各种挑战感到头痛&#xff1f;想要以更高效、简单的方式开发出专业级的项目&#xff1f;MagicalCoder低代码工具正是你苦心寻找的产品&#xff01;它是一款专为…...

8个不能错过的程序员必备网站,惊艳到我了!!!

程序员是一个需要不断学习的职业&#xff0c;不少朋友每天来逛CSDN、掘金等网站&#xff0c;但一直都抱着“收藏从未停止&#xff0c;学习从未开始”的态度&#xff0c;别骗自己了兄弟。在编程体系中&#xff0c;有很多不错的小工具&#xff0c;可以极大得提升我们的开发效率。…...

Mybatis(二):实现“增删改查”

Mybatis&#xff08;二&#xff09;&#xff1a;实现“增删改查”前言一、MyBatis的增删改查1、添加2、修改3、删除4、查询4.1 查询一个实体4.1 查询集合二、MyBatis获取参数值的两种方式&#xff08;重点&#xff09;1、单个字面量类型的参数2、多个字面量类型的参数3、map集合…...

Faster RCNN 对血液细胞目标检测

目录 1. 介绍 2. 工具函数介绍 utils 2.1 xml 文件的读取 get_label_from_xml 2.2 绘制边界框 draw_bounding_box...

【数据结构】Java实现栈

目录 1. 概念 2. 栈的使用 3. 自己动手实现栈&#xff08;使用动态数组实现栈&#xff09; 1. 创建一个MyStack类 2. push入栈 3. pop出栈 4. 查看栈顶元素 5. 判断栈是否为空与获取栈长 6. toString方法 4. 整体实现 4.1 MyStack类 4.2 Test类 4.3 测试结果 1.…...

【数据结构】排序

作者&#xff1a;✿✿ xxxflower. ✿✿ 博客主页&#xff1a;xxxflower的博客 专栏&#xff1a;【数据结构】篇 语录&#xff1a;⭐每一个不曾起舞的日子&#xff0c;都是对生命的辜负。⭐ 文章目录1.排序1.1排序的概念1.2常见的排序算法2.常见排序算法2.1插入排序2.1.1直接插入…...

过拟合、验证集、交叉验证

过拟合 简单描述&#xff1a;训练集误差小&#xff0c;测试集误差大&#xff0c;模型评估指标的方差&#xff08;variance&#xff09;较大&#xff1b; 判断方式&#xff1a; 1、观察 train set 和 test set 的误差随着训练样本数量的变化曲线。 2、通过training accuracy 和…...

原力计划来了【协作共赢 成就未来】

catalogue&#x1f31f; 写在前面&#x1f31f; 新星计划持续上新&#x1f31f; 原力计划方向&#x1f31f; 原力计划拥抱优质&#x1f31f; AIGC&#x1f31f; 参加新星计划还是原力计划&#x1f31f; 创作成就未来&#x1f31f; 写在最后&#x1f31f; 写在前面 哈喽&#x…...

一文了解Jackson注解@JsonFormat及失效解决

背景 项目中使用WRITE_DATES_AS_TIMESTAMPS: true转换日期格式为时间戳未生效。如下&#xff1a; spring:jackson:time-zone: Asia/Shanghaiserialization:WRITE_DATES_AS_TIMESTAMPS: true尝试是否关于时间的注解是否会生效&#xff0c;使用JsonForma和JsonFiled均失效。 常…...

webpack——使用、分析打包代码

世上本无nodejs js最初是在前端浏览器上运行的语言&#xff0c;js代码一旦脱离了浏览器环境&#xff0c;就无法被运行。直到nodejs的出现&#xff0c;我们在电脑上配置了node环境&#xff0c;就可以让js代码脱离浏览器&#xff0c;在node环境中运行。 浏览器不支持模块化 nodej…...

KubeSphere 容器平台高可用:环境搭建与可视化操作指南

Linux_k8s篇 欢迎来到Linux的世界&#xff0c;看笔记好好学多敲多打&#xff0c;每个人都是大神&#xff01; 题目&#xff1a;KubeSphere 容器平台高可用&#xff1a;环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...

vscode里如何用git

打开vs终端执行如下&#xff1a; 1 初始化 Git 仓库&#xff08;如果尚未初始化&#xff09; git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...

工业安全零事故的智能守护者:一体化AI智能安防平台

前言&#xff1a; 通过AI视觉技术&#xff0c;为船厂提供全面的安全监控解决方案&#xff0c;涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面&#xff0c;能够实现对应负责人反馈机制&#xff0c;并最终实现数据的统计报表。提升船厂…...

【磁盘】每天掌握一个Linux命令 - iostat

目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat&#xff08;I/O Statistics&#xff09;是Linux系统下用于监视系统输入输出设备和CPU使…...

【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)

🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...

零基础设计模式——行为型模式 - 责任链模式

第四部分&#xff1a;行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习&#xff01;行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想&#xff1a;使多个对象都有机会处…...

Java面试专项一-准备篇

一、企业简历筛选规则 一般企业的简历筛选流程&#xff1a;首先由HR先筛选一部分简历后&#xff0c;在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如&#xff1a;Boss直聘&#xff08;招聘方平台&#xff09; 直接按照条件进行筛选 例如&#xff1a…...

AspectJ 在 Android 中的完整使用指南

一、环境配置&#xff08;Gradle 7.0 适配&#xff09; 1. 项目级 build.gradle // 注意&#xff1a;沪江插件已停更&#xff0c;推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...

DBLP数据库是什么?

DBLP&#xff08;Digital Bibliography & Library Project&#xff09;Computer Science Bibliography是全球著名的计算机科学出版物的开放书目数据库。DBLP所收录的期刊和会议论文质量较高&#xff0c;数据库文献更新速度很快&#xff0c;很好地反映了国际计算机科学学术研…...

C++实现分布式网络通信框架RPC(2)——rpc发布端

有了上篇文章的项目的基本知识的了解&#xff0c;现在我们就开始构建项目。 目录 一、构建工程目录 二、本地服务发布成RPC服务 2.1理解RPC发布 2.2实现 三、Mprpc框架的基础类设计 3.1框架的初始化类 MprpcApplication 代码实现 3.2读取配置文件类 MprpcConfig 代码实现…...