iOS 视频压缩 mov转mp4 码率

最近还是因为IM模块的功能,IOS录制MOV视频发送后,安卓端无法播放,迫不得已兼容将MOV视频转为MP4发送。

其中mov视频包括4K/24FPS、4K/30FPS、4K/60FPS、720p HD/30FPS、1080p HD/30FPS、1080p HD/60FPS!

使用AVAssetExportSession作为导出工具,指定压缩质量AVAssetExportPresetMediumQuality,这样能有效的减少视频体积,但是视频画面清晰度比较差,举个例子:一个25秒的1080p视频,经过压缩后从1080p变为320p,大小从34m变成2.6m。

AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:avAsset presetName:AVAssetExportPresetMediumQuality];exportSession.outputURL= url;exportSession.shouldOptimizeForNetworkUse = YES;exportSession.outputFileType = AVFileTypeMPEG4;[exportSessionexportAsynchronouslyWithCompletionHandler:^{switch([exportSessionstatus]) {case AVAssetExportSessionStatusFailed:NSLog(@"Export canceled");break;case AVAssetExportSessionStatusCancelled:NSLog(@"Export canceled");break;case AVAssetExportSessionStatusCompleted:{NSLog(@"Successful!");break;}default:break;}

重新梳理下我们的需求,我们的场景对视频质量要求稍高,对视频的大小容忍比较高,所以将最大分辨率设为720p。

所以我们的压缩设置改为AVAssetExportPreset1280x720,压缩后大小几乎没变,从34m变成32.5m。我们可以用mideaInfo来查看下两个视频文件到底有什么区别,上图为1080p,下图为720p:

由上图可以看到,两个分辨率差别巨大的视频,大小居然差不多,要分析其中的原因首先要了解H264编码。

3.H264编码

关于H264编码的原理可以参考(这篇文章),本文不详细展开,只说明几个参数。

Bit Rate:

比特率是指每秒传送的比特(bit)数。单位为 bps(Bit Per Second),比特率越高,每秒传送数据就越多,画质就越清晰。声音中的比特率是指将模拟声音信号转换成数字声音信号后,单位时间内的二进制数据量,是间接衡量音频质量的一个指标。 视频中的比特率(码率)原理与声音中的相同,都是指由模拟信号转换为数字信号后,单位时间内的二进制数据量。

所以选择适合的比特率是压缩视频大小的关键,比特率设置太小的话,视频会变得模糊,失真。比特率太高的话,视频数据太大,又达不到我们压缩的要求。

Format profile:

作为行业标准,H.264编码体系定义了4种不同的Profile(类):Baseline(基线类),Main(主要类), Extended(扩展类)和High Profile(高端类)(它们各自下分成许多个层):
Baseline Profile 提供I/P帧,仅支持progressive(逐行扫描)和CAVLC;
Extended Profile 提供I/P/B/SP/SI帧,仅支持progressive(逐行扫描)和CAVLC;
Main Profile 提供I/P/B帧,支持progressive(逐行扫描)和interlaced(隔行扫描),提供CAVLC或CABAC;
High Profile (也就是FRExt)在Main Profile基础上新增:8x8 intra prediction(8x8 帧内预测), custom quant(自定义量化), lossless video coding(无损视频编码), 更多的yuv格式(4:4:4...);

从压缩比例来说 从压缩比例来说,baseline< main < high,由于上图中720p是Main@L3.1,1080p是High@L4,这就是明明分辨率不一样,但是压缩后的大小却差不多的原因。

关于iPhone设备对的支持

  • iPhone 3GS 和更早的设备支持 Baseline Profile level 3.0 及更低的级别

  • iPhone 4S 支持 High Profile level 4.1 及更低的级别

  • iPhone 5C 支持 High Profile level 4.1 及更低的级别

  • iPhone 5S 支持 High Profile level 4.1 及更低的级别

  • iPad 1 支持 Main Profile level 3.1 及更低的级别

  • iPad 2 支持 Main Profile level 3.1 及更低的级别

  • iPad with Retina display 支持 High Profile level 4.1 及更低的级别

  • iPad mini 支持 High Profile level 4.1 及更低的级别

GOP

GOP 指的就是两个I帧之间的间隔。
在视频编码序列中,主要有三种编码帧:I帧、P帧、B帧。

  1. I帧即Intra-coded picture(帧内编码图像帧),不参考其他图像帧,只利用本帧的信息进行编码
  2. P帧即Predictive-codedPicture(预测编码图像帧),利用之前的I帧或P帧,采用运动预测的方式进行帧间预测编码
  3. B帧即Bidirectionallypredicted picture(双向预测编码图像帧),提供最高的压缩比,它既需要之前的图
    像帧(I帧或P帧),也需要后来的图像帧(P帧),采用运动预测的方式进行帧间双向预测编码
      在视频编码序列中,GOP即Group of picture(图像组),指两个I帧之间的距离,Reference(参考周期)指两个P帧之间的距离。一个I帧所占用的字节数大于一个P帧,一个P帧所占用的字节数大于一个B帧。

所以在码率不变的前提下,GOP值越大,P、B帧的数量会越多,平均每个I、P、B帧所占用的字节数就越多,也就更容易获取较好的图像质量;Reference越大,B帧的数量越多,同理也更容易获得较好的图像质量。
  需要说明的是,通过提高GOP值来提高图像质量是有限度的,在遇到场景切换的情况时,H.264编码器会自动强制插入一个I帧,此时实际的GOP值被缩短了。另一方面,在一个GOP中,P、B帧是由I帧预测得到的,当I帧的图像质量比较差时,会影响到一个GOP中后续P、B帧的图像质量,直到下一个GOP开始才有可能得以恢复,所以GOP值也不宜设置过大。
  同时,由于P、B帧的复杂度大于I帧,所以过多的P、B帧会影响编码效率,使编码效率降低。另外,过长的GOP还会影响Seek操作的响应速度,由于P、B帧是由前面的I或P帧预测得到的,所以Seek操作需要直接定位,解码某一个P或B帧时,需要先解码得到本GOP内的I帧及之前的N个预测帧才可以,GOP值越长,需要解码的预测帧就越多,seek响应的时间也越长。
M 和 N :M值表示I帧或者P帧之间的帧数目,N值表示GOP的长度。N的至越大,代表压缩率越大。因为图2中N=15远小于图一中N=30。这也是720p尺寸压缩不理想的原因。

4.解决思路

由上可知压缩视频主要可以采用以下几种手段:

  • 降低分辨率
  • 降低码率
  • 指定高的 Format profile

由于业务指定分辨率为720p,所以我们只能尝试另外两种方法。

降低码率

根据这篇文章Video Encoding Settings for H.264 Excellence,推荐了适合720p的推荐码率为2400~3700之间。之前压缩的文件码率为9979,所以码率还是有很大的优化空间的。

宽屏

非宽屏

指定高的 Format profile

由于现在大部分的设备都支持High Profile level,所以我们可以把Format profileMain Profile level改为High Profile level

现在我们已经知道要做什么了,那么怎么做呢?

5.解决方法

由于之前的AVAssetExportSession不能指定码率和Format profile,我们这里需要使用AVAssetReaderAVAssetWriter

AVAssetReader负责将数据从asset里拿出来,AVAssetWriter负责将得到的数据存成文件。
核心代码如下:

//生成reader 和 writerself.reader = [AVAssetReader.alloc initWithAsset:self.asset error:&readerError];self.writer = [AVAssetWriter assetWriterWithURL:self.outputURL fileType:self.outputFileType error:&writerError];
//视频if (videoTracks.count > 0) {self.videoOutput = [AVAssetReaderVideoCompositionOutput assetReaderVideoCompositionOutputWithVideoTracks:videoTracks videoSettings:self.videoInputSettings];self.videoOutput.alwaysCopiesSampleData = NO;if (self.videoComposition){self.videoOutput.videoComposition = self.videoComposition;}else{self.videoOutput.videoComposition = [self buildDefaultVideoComposition];}if ([self.reader canAddOutput:self.videoOutput]){[self.reader addOutput:self.videoOutput];}//// Video input//self.videoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:self.videoSettings];self.videoInput.expectsMediaDataInRealTime = NO;if ([self.writer canAddInput:self.videoInput]){[self.writer addInput:self.videoInput];}NSDictionary *pixelBufferAttributes = @{(id)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA),(id)kCVPixelBufferWidthKey: @(self.videoOutput.videoComposition.renderSize.width),(id)kCVPixelBufferHeightKey: @(self.videoOutput.videoComposition.renderSize.height),@"IOSurfaceOpenGLESTextureCompatibility": @YES,@"IOSurfaceOpenGLESFBOCompatibility": @YES,};self.videoPixelBufferAdaptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:self.videoInput sourcePixelBufferAttributes:pixelBufferAttributes];}//音频NSArray *audioTracks = [self.asset tracksWithMediaType:AVMediaTypeAudio];if (audioTracks.count > 0) {self.audioOutput = [AVAssetReaderAudioMixOutput assetReaderAudioMixOutputWithAudioTracks:audioTracks audioSettings:nil];self.audioOutput.alwaysCopiesSampleData = NO;self.audioOutput.audioMix = self.audioMix;if ([self.reader canAddOutput:self.audioOutput]){[self.reader addOutput:self.audioOutput];}} else {// Just in case this gets reusedself.audioOutput = nil;}//// Audio input//if (self.audioOutput) {self.audioInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:self.audioSettings];self.audioInput.expectsMediaDataInRealTime = NO;if ([self.writer canAddInput:self.audioInput]){[self.writer addInput:self.audioInput];}}//开始读写[self.writer startWriting];[self.reader startReading];[self.writer startSessionAtSourceTime:self.timeRange.start];//压缩完成的回调__block BOOL videoCompleted = NO;__block BOOL audioCompleted = NO;__weak typeof(self) wself = self;self.inputQueue = dispatch_queue_create("VideoEncoderInputQueue", DISPATCH_QUEUE_SERIAL);if (videoTracks.count > 0) {[self.videoInput requestMediaDataWhenReadyOnQueue:self.inputQueue usingBlock:^{if (![wself encodeReadySamplesFromOutput:wself.videoOutput toInput:wself.videoInput]){@synchronized(wself){videoCompleted = YES;if (audioCompleted){[wself finish];}}}}];}else {videoCompleted = YES;}if (!self.audioOutput) {audioCompleted = YES;} else {[self.audioInput requestMediaDataWhenReadyOnQueue:self.inputQueue usingBlock:^{if (![wself encodeReadySamplesFromOutput:wself.audioOutput toInput:wself.audioInput]){@synchronized(wself){audioCompleted = YES;if (videoCompleted){[wself finish];}}}}];}

其中self.videoInput里的self.videoSettings我们需要对视频压缩参数做设置

self.videoSettings = @
{AVVideoCodecKey: AVVideoCodecH264,AVVideoWidthKey: @1280,AVVideoHeightKey: @720,AVVideoCompressionPropertiesKey: @{AVVideoAverageBitRateKey: @3000000,AVVideoProfileLevelKey: AVVideoProfileLevelH264High40,},
};

封装好的控件可以参考https://blog.csdn.net/yunhuaikong/article/details/133300420?spm=1001.2014.3001.5502。

6.最终效果

通过下图我们可以看到,视频已经成功被压缩成10m左右。结合视频效果,这个压缩成果我们还是很满意的。

压缩后的文件

下面是视频部分画面截图的效果:

                                                                原视频,34m

                                                原来的MediumQuality压缩效果,2.6m

                                                         原来的720p压缩效果,32.5m

                                                        优化后的720p的压缩效果,10m

7.视频转码时遇到的坑

使用 SDAVAssetExportSession 时遇到一个坑,大部分视频转码没问题,部分视频转码会有黑屏问题,最后定位出现问题的代码如下:


- (AVMutableVideoComposition *)buildDefaultVideoComposition
{AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition];AVAssetTrack *videoTrack = [[self.asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];// get the frame rate from videoSettings, if not set then try to get it from the video track,// if not set (mainly when asset is AVComposition) then use the default frame rate of 30float trackFrameRate = 0;if (self.videoSettings){NSDictionary *videoCompressionProperties = [self.videoSettings objectForKey:AVVideoCompressionPropertiesKey];if (videoCompressionProperties){NSNumber *frameRate = [videoCompressionProperties objectForKey:AVVideoAverageNonDroppableFrameRateKey];if (frameRate){trackFrameRate = frameRate.floatValue;}}}else{trackFrameRate = [videoTrack nominalFrameRate];}if (trackFrameRate == 0){trackFrameRate = 30;}videoComposition.frameDuration = CMTimeMake(1, trackFrameRate);CGSize targetSize = CGSizeMake([self.videoSettings[AVVideoWidthKey] floatValue], [self.videoSettings[AVVideoHeightKey] floatValue]);CGSize naturalSize = [videoTrack naturalSize];CGAffineTransform transform = videoTrack.preferredTransform;// Workaround radar 31928389, see https://github.com/rs/SDAVAssetExportSession/pull/70 for more infoif (transform.ty == -560) {transform.ty = 0;}if (transform.tx == -560) {transform.tx = 0;}CGFloat videoAngleInDegree  = atan2(transform.b, transform.a) * 180 / M_PI;if (videoAngleInDegree == 90 || videoAngleInDegree == -90) {CGFloat width = naturalSize.width;naturalSize.width = naturalSize.height;naturalSize.height = width;}videoComposition.renderSize = naturalSize;// center inside{float ratio;float xratio = targetSize.width / naturalSize.width;float yratio = targetSize.height / naturalSize.height;ratio = MIN(xratio, yratio);float postWidth = naturalSize.width * ratio;float postHeight = naturalSize.height * ratio;float transx = (targetSize.width - postWidth) / 2;float transy = (targetSize.height - postHeight) / 2;CGAffineTransform matrix = CGAffineTransformMakeTranslation(transx / xratio, transy / yratio);matrix = CGAffineTransformScale(matrix, ratio / xratio, ratio / yratio);transform = CGAffineTransformConcat(transform, matrix);}// Make a "pass through video track" video composition.AVMutableVideoCompositionInstruction *passThroughInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];passThroughInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, self.asset.duration);AVMutableVideoCompositionLayerInstruction *passThroughLayer = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];[passThroughLayer setTransform:transform atTime:kCMTimeZero];passThroughInstruction.layerInstructions = @[passThroughLayer];videoComposition.instructions = @[passThroughInstruction];return videoComposition;
}
1. transform 不正确引起的黑屏
CGAffineTransform transform = videoTrack.preferredTransform;

1.参考评论区 @baopanpan同学的说法,TZImagePickerController可以解决,找到代码试了一下,确实不会出现黑屏的问题了,代码如下

/// 获取优化后的视频转向信息
- (AVMutableVideoComposition *)fixedCompositionWithAsset:(AVAsset *)videoAsset {AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition];// 视频转向int degrees = [self degressFromVideoFileWithAsset:videoAsset];if (degrees != 0) {CGAffineTransform translateToCenter;CGAffineTransform mixedTransform;videoComposition.frameDuration = CMTimeMake(1, 30);NSArray *tracks = [videoAsset tracksWithMediaType:AVMediaTypeVideo];AVAssetTrack *videoTrack = [tracks objectAtIndex:0];AVMutableVideoCompositionInstruction *roateInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];roateInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, [videoAsset duration]);AVMutableVideoCompositionLayerInstruction *roateLayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];if (degrees == 90) {// 顺时针旋转90°translateToCenter = CGAffineTransformMakeTranslation(videoTrack.naturalSize.height, 0.0);mixedTransform = CGAffineTransformRotate(translateToCenter,M_PI_2);videoComposition.renderSize = CGSizeMake(videoTrack.naturalSize.height,videoTrack.naturalSize.width);[roateLayerInstruction setTransform:mixedTransform atTime:kCMTimeZero];} else if(degrees == 180){// 顺时针旋转180°translateToCenter = CGAffineTransformMakeTranslation(videoTrack.naturalSize.width, videoTrack.naturalSize.height);mixedTransform = CGAffineTransformRotate(translateToCenter,M_PI);videoComposition.renderSize = CGSizeMake(videoTrack.naturalSize.width,videoTrack.naturalSize.height);[roateLayerInstruction setTransform:mixedTransform atTime:kCMTimeZero];} else if(degrees == 270){// 顺时针旋转270°translateToCenter = CGAffineTransformMakeTranslation(0.0, videoTrack.naturalSize.width);mixedTransform = CGAffineTransformRotate(translateToCenter,M_PI_2*3.0);videoComposition.renderSize = CGSizeMake(videoTrack.naturalSize.height,videoTrack.naturalSize.width);[roateLayerInstruction setTransform:mixedTransform atTime:kCMTimeZero];}else {//增加异常处理videoComposition.renderSize = CGSizeMake(videoTrack.naturalSize.width,videoTrack.naturalSize.height);}roateInstruction.layerInstructions = @[roateLayerInstruction];// 加入视频方向信息videoComposition.instructions = @[roateInstruction];}return videoComposition;
}/// 获取视频角度
- (int)degressFromVideoFileWithAsset:(AVAsset *)asset {int degress = 0;NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeVideo];if([tracks count] > 0) {AVAssetTrack *videoTrack = [tracks objectAtIndex:0];CGAffineTransform t = videoTrack.preferredTransform;if(t.a == 0 && t.b == 1.0 && t.c == -1.0 && t.d == 0){// Portraitdegress = 90;} else if(t.a == 0 && t.b == -1.0 && t.c == 1.0 && t.d == 0){// PortraitUpsideDowndegress = 270;} else if(t.a == 1.0 && t.b == 0 && t.c == 0 && t.d == 1.0){// LandscapeRightdegress = 0;} else if(t.a == -1.0 && t.b == 0 && t.c == 0 && t.d == -1.0){// LandscapeLeftdegress = 180;}}return degress;
}
2. naturalSize 不正确的坑

之前用模拟器录屏得到一个视频,调用[videoTrack naturalSize]的时候,得到的size为 (CGSize) naturalSize = (width = 828, height = 0.02734375),明显是不正确的,这个暂时没有找到解决办法,知道的同学可以在下面评论一下。

3. 视频黑边问题

视频黑边应该是视频源尺寸和目标尺寸比例不一致造成的,需要根据原尺寸的比例算出目标尺寸

   CGSize targetSize = CGSizeMake(videoAsset.pixelWidth, videoAsset.pixelHeight);//尺寸过大才压缩,否则不更改targetSizeif (targetSize.width * targetSize.height > 1280 * 720) {int width = 0,height = 0;if (targetSize.width > targetSize.height) {width = 1280;height = 1280 * targetSize.height/targetSize.width;}else {width = 720;height = 720 * targetSize.height/targetSize.width;}targetSize = CGSizeMake(width, height);}else if (targetSize.width == 0 || targetSize.height == 0) {//异常情况处理targetSize = CGSizeMake(720, 1280);}

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

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

相关文章

web前端tips:js继承——寄生式继承

上篇文章给大家分享了 js继承中的 原型式继承 web前端tips&#xff1a;js继承——原型式继承 今天给大家分享一下 js 继承中的 寄生式继承 寄生式继承 寄生式继承&#xff08;Parasitic Inheritance&#xff09;是一种基于原型式的继承方式&#xff0c;它通过创建一个仅用于…

云可观测性安全平台——掌动智能

云可观测性安全平台是一个跨架构、跨平台的可观测性方案&#xff0c;实现对云环境下的细粒度数据可视化&#xff0c;满足安全部门对云内部安全领域的多场景诉求&#xff0c;包括敏感数据动态监管、云网攻击回溯分析、攻击横移风险监控、云异常流量分析。本文将介绍掌动智能云可…

读高性能MySQL(第4版)笔记17_复制(下)

1. 复制切换 1.1. 复制是高可用性的基础 1.1.1. 总是保留一份持续更新的副本数据&#xff0c;会让灾难恢复更简单 1.2. “切换副本”&#xff08;promoting a replica&#xff09;和“故障切换”&#xff08;failing over&#xff09;是同义词 1.2.1. 意味着源服务器不再接…

C语言的学习快速入门

可以按照以下步骤进行&#xff1a; 了解基本概念和语法&#xff1a;C语言是一种结构化的编程语言&#xff0c;了解基本的语法规则对于入门非常重要。可以学习关键字、变量、数据类型、运算符、控制结构等基本概念。学习编程环境&#xff1a;选择合适的编程环境&#xff0c;例如…

ubuntu16编译linux源码内核

一、环境准备 1.1、安装虚拟机ubuntu16 编译内核大概需要20G的磁盘空间&#xff0c;所以硬盘大小尽量大于40G网络适配使用桥接 1.1.1、查看当前内核版本 uname -r1.2、安装samba服务 Samba 是一款数据共享的软件&#xff0c;可用于 Ubuntu 与 Windows 之间共享源代码&#…

性能测试监控指标及分析调优指南

一、哪些因素会成为系统的瓶颈 CPU&#xff1a;如果存在大量的计算&#xff0c;他们会长时间不间断的占用CPU资源&#xff0c;导致其他资源无法争夺到CPU而响应缓慢&#xff0c;从而带来系统性能问题&#xff0c;例如频繁的FullGC&#xff0c;以及多线程造成的上下文频繁的切换…

基于微信小程序的物流快递信息查询平台同城急送小程序(亮点:寄件、发票申请、在线聊天)

文章目录 前言系统主要功能&#xff1a;具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计…

使用git config --global设置用户名和邮件,以及git config的全局和局部配置

文章目录 1. 文章引言2. 全局配置2.1 命令方式2.2 配置文件方式 3. 局部配置3.1 命令方式3.2 配置文件方式 4. 总结 1. 文章引言 我们为什么要设置设置用户名和邮件&#xff1f; 我们在注册github&#xff0c;gitlab等时&#xff0c;一般使用用户名或邮箱&#xff1a; 这个用户…

蓝桥杯每日一题20223.9.26

4407. 扫雷 - AcWing题库 题目描述 分析 此题目使用map等都会超时&#xff0c;所以我们可以巧妙的使用哈希模拟散列表&#xff0c;哈希表初始化为-1首先将地雷读入哈希表&#xff0c;找到地雷的坐标在哈希表中对应的下标&#xff0c;如果没有则此地雷的位置第一次出现&#…

蓝桥杯 题库 简单 每日十题 day10

01 最少砝码 最少砝码 问题描述 你有一架天平。现在你要设计一套砝码&#xff0c;使得利用这些砝码 可以出任意小于等于N的正整数重量。那么这套砝码最少需要包含多少个砝码&#xff1f; 注意砝码可以放在天平两边。 输入格式 输入包含一个正整数N。 输出格式 输出一个整数代表…

Cruise 从零搭建模型

第一步&#xff0c;新建一个project&#xff1a; 下面添加version&#xff1a; 将该新建的task加载进来&#xff0c;然后保存&#xff1a; 保存完之后&#xff0c;文件夹内多了很多内容&#xff1a; .prj 文件是工程文件。 .bdf 是存放模型里面的数据的文件。 可以看出&#…

三、git的安装和配置

一、安装 1.官网下载&#xff1a;https://git-scm.com/download 下载最新版本&#xff0c;点击红框或篮筐处即可 2.点击下载好的安装包安装这个软件 3.一直点击next&#xff0c;直到出现install&#xff0c;点击install&#xff0c;安装完成后点击finish&#xff1a; 下载完成…

Bootstrap的弹性盒子布局学习笔记

Bootstrap的弹性盒子布局学习笔记 目录 01-综述02-利用类d-flex与类d-inline-flex将容器定义为弹性盒子03-对弹性容器的的元素在水平方向上进行排列顺序设置03-对弹性容器的的元素在垂直方向上进行排列顺序设置04-弹性盒子内所有元素在主轴方向上的对齐方式05-1-弹性盒子内各行…

ubuntu22.04使用共享文件设置

从ubuntu20.04开始&#xff0c;设置共享文件就很麻烦 第一步&#xff1a; 安装samba&#xff1a; sudo apt install samba第二步; 创建一个共享文件夹 我以桌面Desktop为例子 第三步&#xff1a; 设置密码&#xff1a; sudo smbpasswd -a ygc第四步&#xff1a; sudo vim …

基于云服务器 EC2 的云上堡垒机的设计和自动化实现

背景 在很多企业的实际应用场景中&#xff0c;特别是金融类的客户&#xff0c;大部分的应用都是部署在私有子网中&#xff0c;如何能够让客户的开发人员和运维人员从本地的数据中心中安全的访问云上资源&#xff0c;堡垒机是一个很好的选择。传统堡垒机的核心实现原理是基于 S…

windows:批处理bat入门

文章目录 什么是BAT常用命令与语法help与/?titlecolormodeechopausecallremset/a/p gotostartifif errorlevel for普通用法for /l 用法for /d用法for /r用法for /f用法in (file)delims和tokensskipeolusebackq 变量扩展变量延迟 setlocalshiftdirrd&#xff08;删除文件夹&…

C#中的for和foreach的探究与学习

一:语句及表示方法 for语句: for(初始表达式;条件表达式;增量表达式) {循环体 }foreach语句: foreach(数据类型 变量 in 数组或集合) {循环体 }理解 1.从程序逻辑上理解,foreach是通过指针偏移实现的(最初在-1位置,每循环一次,指针就便宜一个单位),而for循环是通

跨类型文本文件,反序列化与类型转换的思考

文章目录 应用场景序列化 - 对象替换原内容&#xff0c;方便使用编写程序取得结果数组 序列化 - JSON 应用场景 在编写热更新的时候&#xff0c;我发现了一个古早的 ini 文件&#xff0c;记录了许多有用的数据 由于使用的语言年份较新&#xff0c;没有办法较好地对 ini 文件的…

Leetcode算法题练习(一)

目录 一、前言 二、移动零 三、复写零 四、快乐数 五、电话号码的字母组合 六、字符串相加 一、前言 大家好&#xff0c;我是dbln&#xff0c;从本篇文章开始我就会记录我在练习算法题时的思路和想法。如果有错误&#xff0c;还请大家指出&#xff0c;帮助我进步。谢谢&…

Library ‘iconv2.4.0‘ not found 问题及解决方法

今天升级了一下Mac mini 和Xcode&#xff0c;运行项目就报Library iconv2.4.0 not found的错误 mac mini 升级&#xff1a;13.0 --> 13.6 xcode升级到&#xff1a;15.0(15A240d) 可以肯定 项目在旧版本下&#xff0c;是能通过编译 并且能运行的。 废话不多说&#xff0c…