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

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);}

相关文章:

iOS 视频压缩 mov转mp4 码率

最近还是因为IM模块的功能&#xff0c;IOS录制MOV视频发送后&#xff0c;安卓端无法播放&#xff0c;迫不得已兼容将MOV视频转为MP4发送。 其中mov视频包括4K/24FPS、4K/30FPS、4K/60FPS、720p HD/30FPS、1080p HD/30FPS、1080p HD/60FPS&#xff01; 使用AVAssetExportSessi…...

基于Esp32-cam在无外部 PIR 传感器情况下实现运动检测(一)

关于无需外部 PIR 传感器的 Esp32-cam 运动检测的世界最佳指南。从 A 到 Z 您需要了解的一切 无需外部 PIR 传感器的 Esp32-cam 运动检测一直是该网站上最热门的话题。 几个月来我付出了很大的努力来完善本指南,但它始终感觉不够好。 所以我继续工作。现在我感到很满足。 现在…...

安卓recovery流程分析(编译、界面、图片)

目录 recovery 界面菜单 recovery 界面操作 recovery 启动流程 recovery 编译makefile recovery 图片大小 ramdisk、boot.img、recovery.img之间的关系 authordaisy.skye的博客_CSDN博客-嵌入式,Qt,Linux领域博主 recovery 界面菜单 recovery 界面显示 android recoveryuse …...

唤醒手腕 2023年 B 站课程 Golang 语言详细教程笔记(更新中)

0001、1000集GO语言Flag毒誓 唤醒手腕UP猪Pig目标花费1000集进行讲解Go语言视频学习教程&#xff08;有趣的灵魂&#xff0c;适合小白&#xff0c;不适合巨佬&#xff09;&#xff0c;从2023年3月19日开始&#xff0c;将会一直每天更新&#xff0c;准备在2024年5月1日之前更新…...

Qt获取屏幕(桌面)的大小或分辨率

Qt提供QDesktopWidget和QScreen两个类获取屏幕大小。Qt5开始&#xff0c;QDesktopWidget官方不建议使用&#xff0c;改为QScreen。Qt 6.0 及之后版本&#xff0c;QDesktopWidget 已从QtWidgets 模块中被彻底移除。 QDesktopWidget QDesktopWidget 提供了详细的位置信息&#…...

第4讲:vue内置命令(文本插值,属性绑定,v-text,v-html)

MVVM 什么是MVVM&#xff1f; MVVM是Model-View-ViewModel的简写。它本质上就是MVC 的改进版。MVVM 就是将其中的View 的状态和行为抽象化&#xff0c;让我们将视图 UI 和业务逻辑分开。 View层&#xff1a; 视图层 在我们前端开发中&#xff0c;通常就是 DOM 层。 主要的作用是…...

vue实现自动生成路由,非手动创建,含避坑点

自动生成路由js文件&#xff1a;autoRouter.js let routeArr []; let baseUrl /components/settingManagement/; const content require.context(../components/settingManagement/, true, /\.vue$/); content.keys().forEach(e > {const path e.substring(e.indexOf(/…...

数据挖掘note(赵老师语录)

&#xff08;一&#xff09; 数据挖掘一般分为机器学习和统计学习&#xff0c;大数据学的课程一般是关于机器学习&#xff0c;我们学的浅&#xff0c;主要关于统计学习&#xff0c;示意图如下所示&#xff1a; 这是一个大数据时代&#xff0c;但是数据挖掘的利用率不足0.5%&am…...

秋招在线人才测评考什么内容?

又是一年招聘季&#xff0c;各大高校都会组织校园招聘&#xff0c;这次我们就来了解一下秋季校园招聘究竟考什么。近些年来校园秋招已经广泛采用在线测评&#xff0c;尤其各行业龙头大厂们&#xff0c;网申、在线测评、小组无领导讨论&#xff0c;一面二面......各类纷杂的面试…...

LeetCode算法二叉树—236. 二叉树的最近公共祖先

目录 236. 二叉树的最近公共祖先 代码&#xff1a; 运行结果&#xff1a; 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点 p、q&#xff0c;最近公共祖先表示为一个节点 x&#xff0c;满足…...

Qt事件处理

1. 事件 众所周知Qt是一个基于C的框架&#xff0c;主要用来开发带窗口的应用程序&#xff08;不带窗口的也行&#xff0c;但不是主流&#xff09;。我们使用的基于窗口的应用程序都是基于事件&#xff0c;其目的主要是用来实现回调&#xff08;因为只有这样程序的效率才是最高…...

宝塔nginx搭建Ftp文件服务器

一&#xff1a;创建FTP 填入账号密码后&#xff0c;选择根目录&#xff0c;这个根目录就是nginx要代理的目录 二&#xff1a;配置nginx root的地址就是上面填的FTP根目录 三&#xff1a;http访问 服务器ip端口号加图片 例如我放了一个320.jp 我服务器ip是110.120.120.120 那…...

SAP和APS系统订单BOM核对(SAP配置BOM攻略九)

配置订单BOM因为要考虑历史ECN、特征语法、BOM语法&#xff0c;是最复杂的一个算法结果&#xff0c;为了摆脱这种被动的场景&#xff0c;博主开发了一个被动核对数据的程序来保障数据质量。 今天是APS系统上线的第三天&#xff0c;我们的核对程序在昨天上线&#xff0c;面对大量…...

ExcelServer EXCEL服务器使用- 用户、角色权限配置

Excel文件服务器搭建 搭建Excel服务器 1、登录 默认 用户名 Admin 密码 3 2、角色管理 添加修改角色 角色配置在 系统管理->角色.fexm文件夹下 可以像修改excel文件一样 修改角色 3、用户管理 添加修改用户 用户的修改在 系统管理->用户.fexm 可以像excel一样编辑用户…...

JOSEF约瑟 静态中间继电器JZY-402 JZJ-404 AC220V 触点形式两开两闭

系列型号&#xff1a; JZY(J)-400静态中间继电器 JZ-Y-401静态中间继电器JZ-Y-402静态中间继电器 JZ-Y-403静态中间继电器JZ-Y-404静态中间继电器 JZ-Y-405静态中间继电器JZ-Y-406静态中间继电器 JZ-Y-407静态中间继电器JZ-Y-408静态中间继电器 JZ-Y-409静态中间继电器JZ…...

C#并发编程的实现方式

一、多线程&#xff1a;是一种并发编程技术&#xff0c;它允许一个应用程序同时执行多个线程。每个线程都有自己的指令集和堆栈&#xff0c;可以在不同的CPU核心上并行运行&#xff0c;或者在一个CPU核心上通过时间片轮转的方式交替运行。多线程的主要优点是可以利用多核处理器…...

qemu源码下载和安装

1、QEMU源码下载 1、官网&#xff1a;https://www.qemu.org/&#xff1b; 2、在“Full of releases”中可以找到以往发布过的版本&#xff1b; 2、源码编译 # 配置命令&#xff0c;生成Makefile。其中--target-list指定编译哪些些架构对应的目录&#xff0c;默认是所有架构都编…...

计算机,软件工程,网络工程,大数据专业毕业设计选题有哪些(附源码获取)

计算机&#xff0c;软件工程&#xff0c;网络工程&#xff0c;大数据专业毕业设计选题有哪些?&#xff08;附源码获取&#xff09; ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于J…...

CyclicBarrier 、CountDownLatch 、Semaphore 的用法

1 CountDownLatch&#xff08;线程计数器 &#xff09; CountDownLatch类位于java.util.concurrent 包下&#xff0c;利用它可以实现类似计数器的功能。比如有一个任务 A&#xff0c;它要等待其他 4 个任务执行完毕之后才能执行&#xff0c;此时就可以利用 CountDownLatch 来实…...

RestTemplate发送HTTPS请求

RestTemplate发送HTTPS请求 基础知识&#xff1a; Https原理与工作流程及证书校验&#xff1a;https://www.cnblogs.com/zjdxr-up/p/14359904.html 忽略ssl证书的方式配置&#xff1a; import lombok.extern.slf4j.Slf4j;import org.springframework.http.client.SimpleClien…...

图像练习-矩形4点OpenCV(01)

提取出里面最大矩形的四个顶点坐标 源图像 结果展示 代码 void getLine(std::vector<int>& data, int threshold) {for (int x 0; x < data.size(); x){if (0 data[x]){continue;}int maxValue 0, maxLoc -1, i -1;for (i x; i < data.size(); i){if …...

不同层设置不同学习率

使用预训练模型时&#xff0c;可能需要将 &#xff08;1&#xff09;预训练好的 backbone 的 参数学习率设置为较小值&#xff0c; &#xff08;2&#xff09;而backbone 之外的部分&#xff0c;需要使用较大的学习率。 from collections import OrderedDict import torch.nn …...

剑指offer32Ⅰ:从上到下打印二叉树

题目描述 从上到下按层打印二叉树&#xff0c;同一层的节点按从左到右的顺序打印&#xff0c;每一层打印到一行。 例如: 给定二叉树: [3,9,20,null,null,15,7], 3 / \ 9 20 / \ 15 7 返回其层次遍历结果&#xff1a; [3,9,20,15,7] 提示&#xff1a; 节…...

【VUE复习·8】v-if;v-show高级

总览 1.v-if 与其变种 v-else-if&#xff1b;v-else 2.v-show 3.v-if 与 v-show 的区别和应用场景 一、v-if 这样用&#xff08;使用 data 或 函数 来驱动它&#xff09; 1.v-if v-if 的用法很简单&#xff0c;它判断的是后面语句的 boolean 值&#xff0c;用来控制 DOM 元…...

线程同步需要注意什么?

线程同步是多线程编程中的重要概念,用于确保多个线程能够正确地协同工作而不会引发数据竞争或不一致的问题。以下是在线程同步时需要注意的关键要点: 共享资源:确保只有在多个线程之间共享的资源需要同步。不是所有的数据都需要同步,只有当多个线程同时访问并修改某个数据时…...

力扣算法题:35、搜索插入位置.java版

版本说明 当前版本号[20230928]。 版本修改说明20230928初版 35.搜索插入位置 点击此处跳转到力扣页面 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 请必…...

七、热力图展示

在开发3d模型之中&#xff0c;热力图是非常常见的需求&#xff0c;比如需要了解人口密度&#xff0c;空气质量&#xff0c;热力分布等这些都需要热力图来展示&#xff0c;那么3d常见的热力图是怎么实现的呢&#xff0c;现在我们就来看看。先看效果图。 思路&#xff1a; 1引入h…...

基于微信小程序的新闻发布平台小程序设计与实现(源码+lw+部署文档+讲解等)

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

【论文阅读】Directional Connectivity-based Segmentation of Medical Images

目录 摘要介绍方法效果结论 论文&#xff1a;Directional Connectivity-based Segmentation of Medical Images 代码&#xff1a;https://github.com/zyun-y/dconnnet 摘要 出发点&#xff1a;生物标志分割中的解剖学一致性对许多医学图像分析任务至关重要。 之前工作的问题&…...

借“牛油果”爆款出圈,甜啦啦的底牌只是“价格”?

上架10日&#xff0c;累计销量超过500万杯。近日&#xff0c;甜啦啦新品“超牛牛油果”瞬间成为门店新晋“爆款”。势头正劲的甜啦啦乘胜追击&#xff0c;袒露了自己的新目标&#xff0c;计划2025年进军北美、欧洲等地区&#xff0c;并在同年开启上市征途。 甜啦啦袒露的新目标…...

网站开发的安全性原则/搜索引擎营销的英文简称

git reset soft,hard,mixed之区别深解 git reset --hard 强制更新覆盖本地 GIT reset命令&#xff0c;似乎让人很迷惑&#xff0c;以至于误解&#xff0c;误用。但是事实上不应该如此难以理解&#xff0c;只要你理解到这个命令究竟在干什么。 首先我们来看几个术语 HEAD这是当…...

怎么做动态网站/网络推广官网首页

https://www.cnblogs.com/alimjan/p/7798295.html https://blog.csdn.net/qq_43847542/article/details/93412505 https://blog.csdn.net/qq_33696345/article/details/79989880 设计说明书以及功能演化 1&#xff1a; 单线程实现网络聊天室----》多线程实现----》线程池实现…...

wordpress 评论后/营销策略分析论文

小伙伴们在面试的时候&#xff0c;有一个特别常见的问题&#xff0c;那就是数据库的回表。什么是回表&#xff1f;为什么需要回表&#xff1f; 今天松哥就来和大家聊一聊这个话题。 1. 索引结构 要搞明白这个问题&#xff0c;需要大家首先明白 MySQL 中索引存储的数据结构。…...

套模板做网站教程/seo超级外链工具

分组查询 关键字&#xff1a;group by 后面根列名&#xff0c;通常和聚合函数一起使用 聚合函数&#xff1a; max(列名) --------统计该列的最大值 min(列名) --------统计该列的最大值 avg(列名) --------统计该列的平均值 sum(列名) --------统计该列的和 count(列名) ----…...

学校网站 功能/全国新闻媒体发稿平台

物联网时代&#xff0c;拼的是生态。 6月23日下午&#xff0c;海尔宣布开放U智慧生活平台的SDK软件包&#xff0c;它为开发者搭建的开放的开发平台“海极网”2.0正式上线&#xff0c;以整合更多的生态资源。 海尔U推广总监邹峘浩表示&#xff0c;智能家居概念在前两年被爆炒&am…...

wordpress 第三性/广州优化seo

如何在SqlServer中获取前端连接的IP地址&#xff0c;计算机名等信息 点击打开链接 在一些需求中&#xff0c;可能我们需要知道连接到SqlServer的前端程序的一些系统信息&#xff0c;比如前端连接的计算机名称&#xff0c;IP地址&#xff0c;什么时候开始请求连接&#xff0c;…...