Android音视频编码(2)

Android本身提供了音视频编解码工具,很多时候是不需要第三方工具的,比如ffmpeg, OpenCV等,在android中引入第三库比较复杂,在Android音视频编码中介绍了如何引入第三方库libpng来进行进行图片处理,同时引入这些第三方库,是程序结构变得复杂。

本文介绍的音视频编解码利用的就是android自带的MediaCodec。视频编码之后,你可以对视频做任何形式的处理,比如添加广告,剪辑等等

MediaCodec

官方文档: MediaCodec

MediaCodec class can be used to access low-level media codecs, i.e. encoder/decoder components. It is part of the Android low-level multimedia support infrastructure (normally used together with MediaExtractor, MediaSync, MediaMuxer, MediaCrypto, MediaDrm, Image, Surface, and AudioTrack.)

在这里插入图片描述
在其生命周期中,编解码器概念上存在三种状态:停止、执行和释放。停止状态实际上是三种状态的集合:未初始化、已配置和错误。而执行状态在概念上分为三个子状态:刷新、运行和流结束。
在这里插入图片描述

使用Buffers进行异步处理

Since Build.VERSION_CODES.LOLLIPOP, the preferred method is to process data asynchronously by setting a callback before calling configure.

从Android5.0之后,谷歌就建议使用异步的方式。
在这里插入图片描述

 MediaCodec codec = MediaCodec.createByCodecName(name);MediaFormat mOutputFormat; // member variablecodec.setCallback(new MediaCodec.Callback() {@Overridevoid onInputBufferAvailable(MediaCodec mc, int inputBufferId) {ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);// fill inputBuffer with valid data…codec.queueInputBuffer(inputBufferId,);}@Overridevoid onOutputBufferAvailable(MediaCodec mc, int outputBufferId,) {ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A// bufferFormat is equivalent to mOutputFormat// outputBuffer is ready to be processed or rendered.…codec.releaseOutputBuffer(outputBufferId,);}@Overridevoid onOutputFormatChanged(MediaCodec mc, MediaFormat format) {// Subsequent data will conform to new format.// Can ignore if using getOutputFormat(outputBufferId)mOutputFormat = format; // option B}@Overridevoid onError() {}@Overridevoid onCryptoError() {}});codec.configure(format,);mOutputFormat = codec.getOutputFormat(); // option Bcodec.start();// wait for processing to completecodec.stop();codec.release();

Since Build.VERSION_CODES.LOLLIPOP, you should retrieve input and output buffers using getInput/OutputBuffer(int) and/or getInput/OutputImage(int) even when using the codec in synchronous mode

在5.0之前,使用同步的方式。通过 getInput/OutputBuffer(int) 或者getInput/OutputImage(int) 来获取输入输出流。

MediaCodec codec = MediaCodec.createByCodecName(name);codec.configure(format,);MediaFormat outputFormat = codec.getOutputFormat(); // option Bcodec.start();for (;;) {int inputBufferId = codec.dequeueInputBuffer(timeoutUs);if (inputBufferId >= 0) {ByteBuffer inputBuffer = codec.getInputBuffer();// fill inputBuffer with valid data…codec.queueInputBuffer(inputBufferId,);}int outputBufferId = codec.dequeueOutputBuffer();if (outputBufferId >= 0) {ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A// bufferFormat is identical to outputFormat// outputBuffer is ready to be processed or rendered.…codec.releaseOutputBuffer(outputBufferId,);} else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {// Subsequent data will conform to new format.// Can ignore if using getOutputFormat(outputBufferId)outputFormat = codec.getOutputFormat(); // option B}}codec.stop();codec.release();

音视频编解码

这里使用同步编码,异步解码。

使用场景是需要截取某一段视频某个时间段的内容,则需要首先对原视频进行解码,获取相应时间段的开始帧和结束帧率。然后根据从开始帧和结束帧对视频进行重新编码。

关于视频帧

在这里插入图片描述
I帧又称帧内编码帧,是一种自带全部信息的独立帧,无需参考其他图像便可独立进行解码,可以简单理解为一张静态画面。视频序列中的第一个帧始终都是I帧,因为它是关键帧

P帧又称帧间预测编码帧,需要参考前面的I帧才能进行编码。表示的是当前帧画面与前一帧(前一帧可能是I帧也可能是P帧)的差别

B帧又称双向预测编码帧,也就是B帧记录的是本帧与前后帧的差别。也就是说要解码B帧,不仅要取得之前的缓存画面,还要解码之后的画面,通过前后画面的与本帧数据的叠加取得最终的画面。

这里直接使用获取的帧,可能是i,p,b帧,根据场景需要可以自己修改。

同步音视频编码
public class VideoEncoder {private static final String TAG = "VideoEncoder";private final static String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;private static final long DEFAULT_TIMEOUT_US = 10000;private MediaCodec mEncoder;private MediaMuxer mMediaMuxer;private int mVideoTrackIndex;private boolean mStop = false;private AudioEncode mAudioEncode;public void init(String outPath, int width, int height) {try {mStop = false;mVideoTrackIndex = -1;mMediaMuxer = new MediaMuxer(outPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);mEncoder = MediaCodec.createEncoderByType(MIME_TYPE);MediaFormat mediaFormat = MediaFormat.createVideoFormat(MIME_TYPE, width, height);mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, width * height * 6);mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30);mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);mEncoder.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);mEncoder.start();} catch (Throwable e) {e.printStackTrace();}}public void release() {mStop = true;if (mEncoder != null) {mEncoder.stop();mEncoder.release();mEncoder = null;}if (mMediaMuxer != null) {mMediaMuxer.stop();mMediaMuxer.release();mMediaMuxer = null;}if (mAudioEncode != null) {mAudioEncode.release();}}public void encode(byte[] yuv, long presentationTimeUs) {if (mEncoder == null || mMediaMuxer == null) {Log.e(TAG, "mEncoder or mMediaMuxer is null");return;}if (yuv == null) {Log.e(TAG, "input yuv data is null");return;}int inputBufferIndex = mEncoder.dequeueInputBuffer(DEFAULT_TIMEOUT_US);Log.d(TAG, "inputBufferIndex: " + inputBufferIndex);if (inputBufferIndex == -1) {Log.e(TAG, "no valid buffer available");return;}ByteBuffer inputBuffer = mEncoder.getInputBuffer(inputBufferIndex);inputBuffer.put(yuv);mEncoder.queueInputBuffer(inputBufferIndex, 0, yuv.length, presentationTimeUs, 0);while (!mStop) {MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();int outputBufferIndex = mEncoder.dequeueOutputBuffer(bufferInfo, DEFAULT_TIMEOUT_US);Log.d(TAG, "outputBufferIndex: " + outputBufferIndex);if (outputBufferIndex >= 0) {ByteBuffer outputBuffer = mEncoder.getOutputBuffer(outputBufferIndex);// write head infoif (mVideoTrackIndex == -1) {Log.d(TAG, "this is first frame, call writeHeadInfo first");mVideoTrackIndex = writeHeadInfo(outputBuffer, bufferInfo);}if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {Log.d(TAG, "write outputBuffer");mMediaMuxer.writeSampleData(mVideoTrackIndex, outputBuffer, bufferInfo);}mEncoder.releaseOutputBuffer(outputBufferIndex, false);break; // 跳出循环}}}public void encodeAudio() {//音频混合mAudioEncode.encodeAudio();release();}private int writeHeadInfo(ByteBuffer outputBuffer, MediaCodec.BufferInfo bufferInfo) {byte[] csd = new byte[bufferInfo.size];outputBuffer.limit(bufferInfo.offset + bufferInfo.size);outputBuffer.position(bufferInfo.offset);outputBuffer.get(csd);ByteBuffer sps = null;ByteBuffer pps = null;for (int i = bufferInfo.size - 1; i > 3; i--) {if (csd[i] == 1 && csd[i - 1] == 0 && csd[i - 2] == 0 && csd[i - 3] == 0) {sps = ByteBuffer.allocate(i - 3);pps = ByteBuffer.allocate(bufferInfo.size - (i - 3));sps.put(csd, 0, i - 3).position(0);pps.put(csd, i - 3, bufferInfo.size - (i - 3)).position(0);}}MediaFormat outputFormat = mEncoder.getOutputFormat();if (sps != null && pps != null) {outputFormat.setByteBuffer("csd-0", sps);outputFormat.setByteBuffer("csd-1", pps);}int videoTrackIndex = mMediaMuxer.addTrack(outputFormat);mAudioEncode = new AudioEncode(mMediaMuxer);Log.d(TAG, "videoTrackIndex: " + videoTrackIndex);mMediaMuxer.start();return videoTrackIndex;}
}

音频编码:

public class AudioEncode {private  MediaMuxer mediaMuxer;private  int audioTrack;private MyExtractor audioExtractor;private MediaFormat audioFormat;public AudioEncode(MediaMuxer mediaMuxer) {audioExtractor = new MyExtractor(Constants.VIDEO_PATH);audioFormat = audioExtractor.getAudioFormat();if (audioFormat != null) {audioTrack = mediaMuxer.addTrack(audioFormat);}this.mediaMuxer = mediaMuxer;}@SuppressLint("WrongConstant")public void encodeAudio() {MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024);if (audioFormat != null) {//写完视频,再把音频混合进去int audioSize = 0;//读取音频帧的数据,直到结束while ((audioSize = audioExtractor.readBuffer(buffer, false)) > 0) {bufferInfo.offset = 0;bufferInfo.size = audioSize;bufferInfo.presentationTimeUs = audioExtractor.getSampleTime();bufferInfo.flags = audioExtractor.getSampleFlags();mediaMuxer.writeSampleData(audioTrack, buffer, bufferInfo);}}}public void  release() {this.audioExtractor.release();}
}
异步音视频解码

要使用sleepRender进行时间同步。

/*** describe:异步解码*/
public class AsyncVideoDecode extends BaseAsyncDecode {private static final String TAG = "AsyncVideoDecode";private Surface mSurface;private long mTime = -1;private int mOutputFormat = Constants.COLOR_FORMAT_NV12;private byte[] mYuvBuffer;private Map<Integer, MediaCodec.BufferInfo> map =new ConcurrentHashMap<>();private boolean writeAudioFlag = false;public AsyncVideoDecode(SurfaceTexture surfaceTexture, long progress) {super(progress);mSurface = new Surface(surfaceTexture);}private ByteArrayOutputStream outStream = new ByteArrayOutputStream();static VideoEncoder mVideoEncoder = null;@Overridepublic void start(){super.start();mediaCodec.setCallback(new MediaCodec.Callback() {@Overridepublic void onInputBufferAvailable(@NonNull MediaCodec codec, int index) {ByteBuffer inputBuffer = mediaCodec.getInputBuffer(index);int size = extractor.readBuffer(inputBuffer, true);long st = extractor.getSampleTime();if (size >= 0) {codec.queueInputBuffer(index,0,size,st,extractor.getSampleFlags());} else {//结束codec.queueInputBuffer(index,0,0,0,BUFFER_FLAG_END_OF_STREAM);}}@Overridepublic void onOutputBufferAvailable(@NonNull MediaCodec codec, int index, @NonNull MediaCodec.BufferInfo info) {Message msg = new Message();msg.what = MSG_VIDEO_OUTPUT;Bundle bundle = new Bundle();bundle.putInt("index",index);bundle.putLong("time",info.presentationTimeUs);if (info.flags == BUFFER_FLAG_END_OF_STREAM)  {if (mVideoEncoder != null)  {mVideoEncoder.encodeAudio();}}//当MediaCodeC配置了输出Surface时,此值返回nullImage image = codec.getOutputImage(index);Rect rect = image.getCropRect();
//                if (image != null) {
//                    Rect rect = null;
//                    try {
//                        rect = image.getCropRect();
//                    } catch (Exception e) {
//                        throw new RuntimeException(e);
//                    }if (mYuvBuffer == null) {mYuvBuffer = new byte[rect.width()*rect.height()*3/2];}
                    YuvImage yuvImage = new YuvImage(ConverUtils.getDataFromImage(image), ImageFormat.NV21, rect.width(), rect.height(), null);
                    yuvImage.compressToJpeg(rect, 100, outStream);
                    byte[] bytes = outStream.toByteArray();if (mVideoEncoder == null) {mVideoEncoder = new VideoEncoder();mVideoEncoder.init(Constants.OUTPUT_PATH,  rect.width(), rect.height());}getDataFromImage(image, mOutputFormat, rect.width(), rect.height());
//                    mVideoEncoder.encode(ConverUtils.getDataFromImage(image), info.presentationTimeUs);mVideoEncoder.encode(mYuvBuffer, info.presentationTimeUs);//                }
//                ByteBuffer outputBuffer = codec.getOutputBuffer(index);
//                ConverUtils.convertByteBufferToBitmap(outputBuffer);msg.setData(bundle);mHandler.sendMessage(msg);}@Overridepublic void onError(@NonNull MediaCodec codec, @NonNull MediaCodec.CodecException e) {codec.stop();}@Overridepublic void onOutputFormatChanged(@NonNull MediaCodec codec, @NonNull MediaFormat format) {}});
//        mediaCodec.configure(mediaFormat,mSurface,null,0);mediaCodec.configure(mediaFormat,null,null,0);mediaCodec.start();}private void getDataFromImage(Image image, int colorFormat, int width, int height) {Rect crop = image.getCropRect();int format = image.getFormat();Log.d(TAG, "crop width: " + crop.width() + ", height: " + crop.height());Image.Plane[] planes = image.getPlanes();byte[] rowData = new byte[planes[0].getRowStride()];int channelOffset = 0;int outputStride = 1;for (int i = 0; i < planes.length; i++) {switch (i) {case 0:channelOffset = 0;outputStride = 1;break;case 1:if (colorFormat == Constants.COLOR_FORMAT_I420) {channelOffset = width * height;outputStride = 1;} else if (colorFormat == Constants.COLOR_FORMAT_NV21) {channelOffset = width * height + 1;outputStride = 2;} else if (colorFormat == Constants.COLOR_FORMAT_NV12) {channelOffset = width * height;outputStride = 2;}break;case 2:if (colorFormat == Constants.COLOR_FORMAT_I420) {channelOffset = (int) (width * height * 1.25);outputStride = 1;} else if (colorFormat ==Constants.COLOR_FORMAT_NV21) {channelOffset = width * height;outputStride = 2;} else if (colorFormat == Constants.COLOR_FORMAT_NV12) {channelOffset = width * height + 1;outputStride = 2;}break;default:}ByteBuffer buffer = planes[i].getBuffer();int rowStride = planes[i].getRowStride();int pixelStride = planes[i].getPixelStride();int shift = (i == 0) ? 0 : 1;int w = width >> shift;int h = height >> shift;buffer.position(rowStride * (crop.top >> shift) + pixelStride * (crop.left >> shift));for (int row = 0; row < h; row++) {int length;if (pixelStride == 1 && outputStride == 1) {length = w;buffer.get(mYuvBuffer, channelOffset, length);channelOffset += length;} else {length = (w - 1) * pixelStride + 1;buffer.get(rowData, 0, length);for (int col = 0; col < w; col++) {mYuvBuffer[channelOffset] = rowData[col * pixelStride];channelOffset += outputStride;}}if (row < h - 1) {buffer.position(buffer.position() + rowStride - length);}}}}@Overrideprotected int decodeType() {return VIDEO;}@Overridepublic boolean handleMessage(@NonNull Message msg) {switch (msg.what){case MSG_VIDEO_OUTPUT:try {if (mTime == -1) {mTime = System.currentTimeMillis();}Bundle bundle = msg.getData();int index = bundle.getInt("index");long ptsTime = bundle.getLong("time");sleepRender(ptsTime,mTime);
//                    if (ptsTime + extractor.getSampleDiffTime() >= progress &&  progress >= ptsTime)
//                    {
//                        writeAudioFlag = true;
//                    }
//                    if (writeAudioFlag) {mediaCodec.releaseOutputBuffer(index, false);
//                    }} catch (Exception e) {e.printStackTrace();}break;default:break;}return super.handleMessage(msg);}/*** 数据的时间戳对齐**/private long sleepRender(long ptsTimes, long startMs) {/*** 注意这里是以 0 为出事目标的,info.presenttationTimes 的单位为微秒* 这里用系统时间来模拟两帧的时间差*/ptsTimes = ptsTimes / 1000;long systemTimes = System.currentTimeMillis() - startMs;long timeDifference = ptsTimes - systemTimes;// 如果当前帧比系统时间差快了,则延时以下if (timeDifference > 0) {try {//todo 受系统影响,建议还是用视频本身去告诉解码器 pts 时间Thread.sleep(timeDifference);} catch (InterruptedException e) {e.printStackTrace();}}return timeDifference;}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/621455.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JVM内存区域详解,一文弄懂JVM内存【内存分布、回收算法、垃圾回收器】

视频讲解地址 学习文档 一、内存区域 区域描述线程私有如何溢出程序计数器为了线程切换后能恢复到正确的执行位置&#xff0c;每个线程都要有一个独立的程序计数器。✅唯一一个不会内存溢出的地方虚拟机栈1. 每个方法执行的时候&#xff0c;Java虚拟机都会同步创建一个栈帧用于…

获取当前设备的IP

背景&#xff1a; 在本地使用自带webUI的项目时&#xff0c;需要制定webUI的访问地址。 一般本地访问使用&#xff1a;127.0.0.1&#xff0c;配置为可以从其他设备访问时&#xff0c;需要指定当前设备的IP&#xff0c;或者指定为0.0.0.0。 例如&#xff1a;使用locust的时候&a…

【Python学习】Python学习18- 方法OS 文件/目录方法

目录 【Python学习】Python学习17- File方法 前言os.access()语法&#xff1a; os.chdir(path)语法 os.chflags(path, flags)语法 os.chmod(path, mode)os.chown(path, uid, gid)os.chroot(path)os.close(fd)os.unlink(path)os.popen(command[, mode[, bufsize]])os.read(fd, …

Python--循环语句

在 Python 中&#xff0c;循环语句用于重复执行一段代码多次。Python 主要提供了两种类型的循环&#xff1a;for 循环和 while 循环。 1. for 循环 for 循环用于遍历可迭代对象&#xff08;如列表、元组、字典、字符串等&#xff09;中的每个元素&#xff0c;并对每个元素执行…

精通业务:资深程序员的核心优势

在IT行业&#xff0c;我们常常听到关于技术实力、项目经验、团队协作等方面的讨论&#xff0c;但有一个重要因素常常被忽视&#xff0c;那就是对业务的了解。 对于资深程序员来说&#xff0c;懂业务和不懂业务之间的区别&#xff0c;犹如一道深邃的鸿沟&#xff0c;决定着他们…

MCS-51---串行通信的特点

目录 一.同步通信和异步通信 1.异步通信 2.同步通信 二.串行通信的方式 1.单工 2.半双工 3.全双工 三.串行通信的速率 四.MCS-51单片机结构 五.串行口的控制 1.串行口控制寄存器(SCON) 2.电源控制寄存器(PCON) 六.波特率的设计 七.串行口的工作方式 1.方式0 2.…

DM数据库安装注意事项

数据库安装注意事项 一、安装前 一些参数需要在数据库创建实例前找用户确认。 参数名参数掩码参数值备注数据页大小PAGE_SIZE32数据文件使用的页大小(缺省使用8K&#xff0c;建议默认&#xff1a;32)&#xff0c;可以为 4K、8K、16K 或 32K 之一&#xff0c;选择的页大小越大…

k8s存储卷之动态

动态pv需要两个组件 1、卷插件&#xff0c;k8s本身支持的动态pv创建不包含NFS&#xff0c;需要声明和安装一个外部插件 Provisioner 存储分配器&#xff0c;动态创建pv&#xff0c;然后根据pvc的请求自动绑定和使用 2、StorageClass&#xff0c;用来定义pv的属性&#xff0c…

选择和训练模型(Machine Learning 研习之十一)

当您看到本文标题时&#xff0c;不禁感叹&#xff0c;总算是到了训练模型这一节了。 是啊&#xff0c;在之前的文章中&#xff0c;我们对数据进行了探索&#xff0c;以及对一个训练集和一个测试集进行了采样&#xff0c;也编写了一个预处理管道来自动清理&#xff0c;准备您的数…

大数据赋能电竞出海企业发展

近几年电竞行业发展迅速&#xff0c;我国单2022年新增近4万家电竞相关企业&#xff0c;竞争十分激烈。中国电竞市场规模在全球占比19%左右&#xff0c;海外有巨大的增量市场&#xff0c;特别是东南亚、中南亚和拉丁美洲是电竞市场增长最快的地区&#xff0c;在2020至2025年期间…

C#,求最长回文字符串的马拉车(Manacher)算法的源代码

一、回文字符串&#xff08;Palindromic String&#xff09; 回文字符串&#xff08;Palindromic String&#xff09;是指前、后向读起来完全相同的字符串。 回文字符串除了答题似乎没有什么用处 :P 二、求解思路 求解字符串的回文子串的基本思路&#xff1a; 1、遍历每个位…

HTML---CSS-引入样式表和选择器

CSS : Cascading Style Sheet 层叠式样式表 HTML 用于控制网页的结构&#xff0c;CSS则用于控制网页的外观&#xff0c;想要做出美观好看的网页&#xff0c;CSS是必须的 引入外部样式表&#xff1a; 它的属性 rel 和 type是固定的 语法&#xff1a; <link rel"styles…

16 SysTick—系统定时器

文章目录 16.0 前言16.1 SysTick 简介16.2 SysTick 寄存器介绍16.2.1 CTRL 控制及状态寄存器16.2.2 RELOAD 重载数值寄存器16.2.3 Current当前数值寄存器16.2.4 CALRB 校准值寄存器16.3 SysTick 定时实验16.3.1 编程要点16.3.2 代码分析16.3.2.1 SysTick 配置库函数16.3.2.2 配…

音频编辑软件:Studio One 6 中文

Studio One 6是一款功能强大的数字音乐制作软件&#xff0c;为用户提供一站式音乐制作解决方案。它具有直观的界面和强大的音频录制、编辑、混音和制作功能&#xff0c;支持虚拟乐器、效果器和第三方插件&#xff0c;可帮助用户实现高质量的音乐创作和制作。同时&#xff0c;St…

Java基础之虚拟机

1、前言 本篇基于网络整理&#xff0c;和自己编辑。在不断的完善补充哦。 2、什么是虚拟机&#xff1f; Java 虚拟机&#xff0c;是一个可以执行 Java 字节码的虚拟机进程。Java 源文件被编译成能被 Java 虚拟机执行的字节码文件( .class )。 Java 被设计成允许应用程序可以运…

格密码基础:SIS问题的定义与理解

目录 一. 介绍 二. SIS问题定义 2.1 直观理解 2.2 数学定义 2.3 基本性质 三. SIS与q-ary格 四. SIS问题的推广 五. Hermite标准型 六. 小结 一. 介绍 short interger solution problem短整数解问题&#xff0c;简称SIS问题。 1996年&#xff0c;Ajtai首次提出SIS问…

【数据结构】排序算法

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:数据结构 ⚙️操作环境:Visual Studio 2022 目录 &#x1f38f;排序的定义 &#x1f38f;排序的稳定性 &#x1f4cc;稳定性的定义 &#x1f4cc;稳定性的意义 &#x1f38f;内排序与外排序 &#x1f38f;八大内排…

GitHub图床搭建

1 准备Github账号 如果没有Github账号需要先在官网注册一个账号 2 创建仓库 在github上创建一个仓库&#xff0c;随便一个普通的仓库就行&#xff0c;选择公共仓库 3 github token获取 github token创建方式可以参考下面的方式&#xff1a; https://www.xichangyou.com/6…

c/c++中static的用法

概述 static&#xff1a; 作为c/c的关键字之一&#xff0c;具有多种含义和应用&#xff0c;static 关键字可用于声明变量、函数、类数据成员和类函数。默认情况下&#xff0c;在所有块的外部定义的对象或变量具有静态持续时间和外部链接。 静态持续时间意味着&#xff0c;在程…

MT1138-MT1150总结

1. 判断闰年方法 year%40&&year%400&#xff01;0||year%4000 #include<bits/stdc.h> using namespace std;int day(int year,int mouth){if(mouth1||mouth3||mouth5||mouth7||mouth8||mouth10||mouth12){return 31;}else if(mouth4||mouth6||mouth9||mouth11)…