看图可知, AVFoundation 拍照功能的中心类是 AVCaptureSession 类,管理视频输入输出流。

音视频,上手就用,直接 AVKit 更加灵活的控制,就用到 AVFoundation


要点:

  • 使用资源(一般就是照片库里面的视频,图片,live photo ),
  • 播放,
  • 捕捉(拍照和录视频)
  • 导出资源( 把处理编辑过的资源,拍的照片,编辑的视频,导出到相册)
  • 编辑 (视频合成, 添加动画) AVComposition

AVFoundation , 视频的加载与导出,大量使用异步。 简单的发消息, 肯定是不行的。阻塞当前线程, 卡顿很久很久。 AVFoundation 就是为了充分利用64位的硬件和多线程设计的。


首先是播放,

播放本地的视频文件, 和远程的视频与流媒体。

本地文件,单个播放

先讲 AVKit 里面的 AVPlayerViewController. AVPlayerViewController 是 ViewController 的子类,

AVPlayerViewController 在 TV OS 上,非常强大。(本文仅介绍 iOS 平台下)

苹果自带的 AVPlayerViewController 里面有很多播放的控件。 回播中,就是播放本地文件中,可以播放、暂停、快进、快退,调整视频的长宽比例( 即画面在屏幕中适中,或者铺满屏幕)。

播放视频,苹果设计的很简单,代码如下:

    //  拿一个 url , 建立一个 AVPlayer 实例let player = AVPlayer(url: "你的 url")//  再建立一个 AVPlayerViewController 实例let playerViewController = AVPlayerViewController()playerViewController.player = queuePlayerpresent(playerViewController, animated: true) {playerViewController.player!.play()}// 这里有一个闭包, 出现了,再播放。

本地文件,多个连续播放

连着放,使用 AVQueuePlayer,把多个视频放在一个视频队列中,依次连续播放 AVQueuePlayer 是 AVPlayer 的子类。 按顺序,播放多个资源。

AVPlayerItem 包含很多视频资源信息,除了资源定位 URI , 还有轨迹信息,视频的持续时长等。

苹果文档上说, AVPlayerItem 用于管理播放器播放的资源的计时和呈现状态。他有一个 AVAsset 播放资源的属性。

   var queue = [AVPlayerItem]()   let videoClip = AVPlayerItem(url: url)queue.append(videoClip)//   queue 队列可以继续添加 AVPlayerItem 实例let queuePlayer = AVQueuePlayer(items: queue)let playerViewController = AVPlayerViewController()playerViewController.player = queuePlayerpresent(playerViewController, animated: true) {playerViewController.player!.play()}

iPad 中的画中画功能

iPad 中的画中画功能,通过给 AVAudioSession 支持后台音效, 在 AppdelegatedidFinishLaunchingWithOptions 中添加下面的这段代码,使用后台模式, 首先在Xcode 的 target 的 Capability 中勾选相关的后台功能。

    let session = AVAudioSession.sharedInstance()do {try session.setCategory(AVAudioSessionCategoryPlayback)try session.setActive(true)} catch let error {print("AVFoundation configuration error: \(error.localizedDescription) \n\n AV 配置 有问题")}// 很有必要这样,因为画中画的视频功能,apple 是当后台任务处理的。

流媒体播放和网络视频播放

本地的资源路径 URL ,替换为网络的 URL, 就可以了。

优化,播放完成后,退出播放界面

   override func viewDidLoad() {super.viewDidLoad()// 添加播放完成的监听NotificationCenter.default.addObserver(self, selector: #selector(playerItemDidReachEnd), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)}//  执行退出的界面控制@objc func playerItemDidReachEnd(){self.presentedViewController?.dismiss(animated: true, completion: {})}

接着来, 拍照, 设置捕捉的 session ,并实时预览。

设置前后摄像头,聚焦与曝光,拍照(静态图片)

摄像用到的核心类是 AVCaptureSession ,应用和 iOS 建立一个视频流的会话。 AVCaptureSession 作为调度中心, 控制设备的输入/输出流, 具体就是相机和麦克风。

AVCaptureDeviceInput 类是视频流的输入源,预览界面呈现的就是他的数据,导出的视频文件也是他负责的。 视频流 session 对象生成后,可以重新配置。视频流 session 的配置信息,这就可以动态修改。视频流 session 的输入输出的路由,也可以动态改。例如,只需要一个 session. 可以导出照片,通过 AVCapturePhotoOutput,可以导出视频文件 AVCaptureMovieFileOutput.

开启视频会话

captureSession.startRunning() 之前,先要添加输入 AVCaptureDeviceInput 和输出 AVCapturePhotoOutput/AVCaptureMovieFileOutput,准备预览界面 AVCaptureVideoPreviewLayer

// 有一个 captureSession 对象
let captureSession = AVCaptureSession()
// 两个输出,输出照片, 和输出视频
let imageOutput = AVCapturePhotoOutput()
let movieOutput = AVCaptureMovieFileOutput()func setupSession() -> Bool{captureSession.sessionPreset = AVCaptureSession.Preset.high// 首先设置 session 的分辨率 。sessionPreset 属性,设置了输出的视频的质量   let camera = AVCaptureDevice.default(for: .video)// 默认的相机是 back-facing camera 朝前方拍摄, 不是自拍的。do {let input = try AVCaptureDeviceInput(device: camera!)if captureSession.canAddInput(input){captureSession.addInput(input)activeInput = input// 添加拍照, 录像的输入}} catch {print("Error settings device input: \(error)")return false}// 设置麦克风let microphone = AVCaptureDevice.default(for: .audio)do{let micInput = try AVCaptureDeviceInput(device: microphone!)if captureSession.canAddInput(micInput){captureSession.addInput(micInput)//   添加麦克风的输入}}catch{print("Error setting device audio input: \(String(describing: error.localizedDescription))")fatalError("Mic")}//  添加两个输出,输出照片, 和输出视频if captureSession.canAddOutput(imageOutput){captureSession.addOutput(imageOutput)}if captureSession.canAddOutput(movieOutput){captureSession.addOutput(movieOutput)}return true}
设置视频会话的预览界面

AVCaptureVideoPreviewLayer 是 CALayer 的子类,用于展示相机拍的界面。

    func setupPreview() {// 配置预览界面 previewLayerpreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)// previewLayeris 通过 captureSession 初始化  // 再设置相关属性, 尺寸和视频播放时的拉伸方式 videoGravitypreviewLayer.frame = camPreview.boundspreviewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFillcamPreview.layer.addSublayer(previewLayer)//  camPreview 是一个 UIView ,铺在 self.view 上面
}
拍, startSession

启动视频流的方法,启动了,就不用管。没启动,就处理 启动视频流是耗时操作,为不阻塞主线程,一般用自定义线程作异步。

let videoQueue = DispatchQueue.global(qos: .default)func startSession(){if !captureSession.isRunning{videoQueue.async {self.captureSession.startRunning()}}}

拍照片,下面的代码是静态图,不是 Live Photo.

var outputSetting = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg])//  静态图的配置func capturePhoto() {guard PHPhotoLibrary.authorizationStatus() == PHAuthorizationStatus.authorized else{PHPhotoLibrary.requestAuthorization(requestAuthorizationHander)return}let settings = AVCapturePhotoSettings(from: outputSetting)imageOutput.capturePhoto(with: settings, delegate: self)
//  imageOutput 输出流里面的采样缓冲中,捕获出静态图}extension ViewController: AVCapturePhotoCaptureDelegate{func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
//  如果视频流的采样缓冲里面有数据,就拆包if let imageData = photo.fileDataRepresentation(){let image = UIImage(data: imageData)let photoBomb = image?.penguinPhotoBomb(image: image!)self.savePhotoToLibrary(image: photoBomb!)//  最后,合成照片保存到系统相册//  这里有一个照片合成,具体见下面的 Github Repo.}else{print("Error capturing photo: \(String(describing: error?.localizedDescription))")}}
}

到自拍了,就是支持前置摄像头,front-facing camera.

首先,要确认手机要有多个摄像头。有多个,就可以切换摄像头输入。 具体套路就是开始配置,修改,与提交修改。 captureSession.beginConfiguration() ,接着写修改,直到 captureSession.commitConfiguration() 提交了,才生效。 类似的还有 CATransaction, 开始,设置,提交,就可以在屏幕上看到刷新的界面了。

    // 配置拍前面(自拍),拍后面@IBAction func switchCameras(_ sender: UIButton) {guard movieOutput.isRecording == false else{return}//  确认手机要有多个摄像头guard let frontCamera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front), let backCamera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else{return;}// 创建新的 AVCaptureDeviceInput ,来切换。更新 captureSession 的配置。do{var input: AVCaptureDeviceInput?//  通过识别当前的摄像头,找出另一个(我们需要的)if activeInput.device == frontCamera{input = try AVCaptureDeviceInput(device: backCamera)}else{input = try AVCaptureDeviceInput(device: frontCamera)}// 得到了新的输入源,就可以开始配置了captureSession.beginConfiguration()// 去掉旧的输入源,即不让当前的摄像头输入captureSession.removeInput(activeInput)// 增加新的输入源,即让其他的摄像头输入if captureSession.canAddInput(input!){captureSession.addInput(input!)activeInput = input}// captureSession.beginConfiguration() 之后,就开始修改,直到下一句提交了,才生效。captureSession.commitConfiguration()}catch{print("Error , switching cameras: \(String(describing: error))")}}

聚焦功能 POI : 点击屏幕,拍照聚焦到兴趣点

具体实现是把屏幕 UI 坐标,也就是预览图层的坐标,转换到相机的坐标系中, 再用预览图层的坐标点,设置聚焦的 point 和 mode 。 配置聚焦,属于用户输入,并要用到手机的摄像头硬件。配置 POI 的时候,可能有干扰 ( 比如后台进程的影响 ),这样就要用锁了。 device.lockForConfiguration() 注意: 自拍是不可以聚焦的。前置摄像头,没有 POI 功能。

// 把屏幕 UI 坐标,转化为预览图层的坐标。@objcfunc tapToFocus(recognizer: UIGestureRecognizer){if activeInput.device.isFocusPointOfInterestSupported{// 得到屏幕中点击的坐标,转化为预览图层里的坐标点let point = recognizer.location(in: camPreview)//  将预览图层中的坐标点,转换到相机的坐标系中let pointOfInterest = previewLayer.captureDevicePointConverted(fromLayerPoint: point)//  自由设置相关 UIshowMarkerAtPoint(point: point, marker: focusMarker)focusAtPoint(pointOfInterest)}}//   用预览图层的坐标点,配置聚焦。func focusAtPoint(_ point: CGPoint){let device = activeInput.device// 首先判断手机能不能聚焦if device.isFocusPointOfInterestSupported , device.isFocusModeSupported(.autoFocus){do{// 锁定设备来配置try device.lockForConfiguration()device.focusPointOfInterest = pointdevice.focusMode = .autoFocusdevice.unlockForConfiguration()// 配置完成,解除锁定}catch{print("Error focusing on POI: \(String(describing: error.localizedDescription))")}}}

拍照曝光功能,双击设置曝光坐标

类似聚焦,具体实现是把屏幕 UI 坐标,也就是预览图层的坐标,转换到相机的坐标系中, 再用预览图层的坐标点,设置曝光的 point 和 mode 。 同聚焦不一样,曝光要改两次 mode. mode 从默认锁定的 .locked 到选定坐标点的连续自动曝光 .continuousAutoExposure, 最后系统调好了,再切换回默认的锁定 .locked 。 因为不知道系统什么时候连续自动曝光处理好,所以要用到 KVO. 监听 activeInput.device 的 adjustingExposure 属性。 当曝光调节结束了,就锁定曝光模式。( 调用时机挺好的, 双击屏幕,手机摄像头自动曝光的时候,就防止干扰。曝光完成后,马上改曝光模式为锁定 。这样就不会老处在曝光中。) (这个有点像监听键盘,那里一般用系统通知。) 配置曝光,属于用户输入,并要用到手机的摄像头硬件。配置曝光的时候,可能有干扰 ( 比如后台进程的影响 ),这样就要用锁了。 device.lockForConfiguration() 其他: 自拍是有曝光效果的

// 单指双击,设置曝光, 更多见下面的 github repo@objcfunc tapToExpose(recognizer: UIGestureRecognizer){if activeInput.device.isExposurePointOfInterestSupported{//  与聚焦一样,得到屏幕中点击的坐标,转化为预览图层里的坐标点let point = recognizer.location(in: camPreview)//  将预览图层中的坐标点,转换到相机的坐标系中let pointOfInterest = previewLayer.captureDevicePointConverted(fromLayerPoint: point)showMarkerAtPoint(point: point, marker: exposureMarker)exposeAtPoint(pointOfInterest)}}private var adjustingExposureContext: String = "Exposure"private let kExposure = "adjustingExposure"func exposeAtPoint(_ point: CGPoint){let device = activeInput.deviceif device.isExposurePointOfInterestSupported, device.isFocusModeSupported(.continuousAutoFocus){do{try device.lockForConfiguration()device.exposurePointOfInterest = pointdevice.exposureMode = .continuousAutoExposure//  先判断手机,能不能锁定曝光。可以就监听手机摄像头的调整曝光属性if device.isFocusModeSupported(.locked){//   同聚焦不一样,曝光要改两次 mode.//  这里有一个不受控制的耗时操作( 不清楚什么时候系统处理好),需要用到 KVOdevice.addObserver(self, forKeyPath: kExposure, options: .new, context: &adjustingExposureContext)// 变化好了, 操作结束device.unlockForConfiguration()}}catch{print("Error Exposing on POI: \(String(describing: error.localizedDescription))")}}}// 使用 KVOoverride func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {//  先确认,监听到的是指定的上下文if context == &adjustingExposureContext {let device = object as! AVCaptureDevice//    如果手机摄像头不处于曝光调整中,也就是完成曝光了,就可以处理了if !device.isAdjustingExposure , device.isExposureModeSupported(.locked){// 观察属性,变化了, 一次性注入调用, 就销毁 KVO// 然后到主队列中异步配置device.removeObserver(self, forKeyPath: kExposure, context: &adjustingExposureContext)DispatchQueue.main.async {do{//  完成后,将曝光状态复原try device.lockForConfiguration()device.exposureMode = .lockeddevice.unlockForConfiguration()}catch{print("Error exposing on POI: \(String(describing: error.localizedDescription))")}   }}}else{super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)}}

其次是拍视频,把视频文件导出到相册

处理 AVFoundation,套路就是配置 session, 添加输入输出, 把视频流的管道打通。 用 device 作为输入,获取信息,用 session 作为输入输出的桥梁,控制与调度,最后指定我们想要的输出类型。 拍视频与拍照不同,会有声音,输入源就要加上麦克风了 AVCaptureDevice.default(for: .audio),视频流的输出就要用到 AVCaptureMovieFileOutput 类了。

拍视频的代码如下:

func captureMovie() {//  首先,做一个确认与切换。当前摄像头不在拍摄中,就拍摄guard movieOutput.isRecording == false else {print("movieOutput.isRecording\n")stopRecording()return;}//  获取视频输出的连接let connection = movieOutput.connection(with: .video)//    控制连接的方位,视频的横竖屏比例与手机的一致  //    点击拍摄按钮拍摄的这一刻,根据当前设备的方向来设置录像的方向if (connection?.isVideoOrientationSupported)!{connection?.videoOrientation = currentVideoOrientation()}// 设置连接的视频自动稳定,手机会选择合适的拍摄格式和帧率if (connection?.isVideoStabilizationSupported)!{connection?.preferredVideoStabilizationMode = AVCaptureVideoStabilizationMode.auto}let device = activeInput.device//  因为需要摄像头能够灵敏地聚焦if device.isSmoothAutoFocusSupported{do{try device.lockForConfiguration()device.isSmoothAutoFocusEnabled = false// 如果设置为 true,   lens movements  镜头移动会慢一些device.unlockForConfiguration()}catch{print("Error setting configuration: \(String(describing: error.localizedDescription))")}}let output = URL.tempURLmovieOutput.startRecording(to: output!, recordingDelegate: self)}

与拍照不同,录像使用的是连接, movieOutput.connection(with: .video).

拍视频,自然会有完成的时候,

AVCaptureFileOutputRecordingDelegate 类的代理方法里面,保存视频文件,更新 UI

outputFileURL 参数, 是系统代理完成回调给开发者的,系统把视频文件写入 app 沙盒的资源定位符。要做的是把沙盒里面的视频文件,拷贝到系统相册。

func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {if let error = error{print("Error, recording movie: \(String(describing: error.localizedDescription))")}else{// 保存到相册, 具体代码见 github reposaveMovieToLibrary(movieURL: outputFileURL)// 更改 UIcaptureButton.setImage(UIImage(named: "Capture_Butt"), for: .normal)//  停止计时器stopTimer()}}
拍视频的时候,能够知道录的怎么样了,比较好。

用计时器记录,有一个 Label 展示

func startTimer(){// 销毁旧的if updateTimer != nil {updateTimer.invalidate()}//  开启新的updateTimer = Timer(timeInterval: 0.5, target: self, selector: #selector(self.updateTimeDisplay), userInfo: nil, repeats: true)RunLoop.main.add(updateTimer, forMode: .commonModes)}

拍照环境较暗,就要亮灯了,都是调整 AVCaptureDevice 类里的属性。

拍照用闪光灯, 用 flashMode, 配置 AVCapturePhotoSettings。 每次拍照,都要新建 AVCapturePhotoSettings. 拍视频用手电筒, 用 TorchMode, 配置的是 device.torchMode 直接修改 AVCaptureDevice 的属性 苹果设计的很好。输出类型决定亮灯模式。 拍照用闪光灯,是按瞬间动作配置。 拍视频,就是长亮了。

// MARK: Flash Modes (Still Photo), 闪光灯func setFlashMode(isCancelled: Bool = false) {let device = activeInput.device// 闪光灯, 只有后置摄像头有。 前置摄像头是,增加屏幕亮度if device.isFlashAvailable{//  这段代码, 就是控制闪光灯的 off, auto , on 三种状态, 来回切换var currentMode = currentFlashOrTorchMode().modecurrentMode += 1if currentMode > 2 || isCancelled == true{currentMode = 0}let new_mode = AVCaptureDevice.FlashMode(rawValue: currentMode)self.outputSetting.flashMode = new_mode!;flashLabel.text = currentFlashOrTorchMode().name}}// MARK: Torch Modes (Video), 手电筒func setTorchMode(isCancelled: Bool = false) {let device = activeInput.deviceif device.hasTorch{//  这段代码, 就是控制手电筒的 off, auto , on 三种状态, 来回切换var currentMode = currentFlashOrTorchMode().modecurrentMode += 1if currentMode > 2 || isCancelled == true{currentMode = 0}let new_mode = AVCaptureDevice.TorchMode(rawValue: currentMode)if device.isTorchModeSupported(new_mode!){do{// 与前面操作类似,需要 lock 一下try device.lockForConfiguration()device.torchMode = new_mode!device.unlockForConfiguration()flashLabel.text = currentFlashOrTorchMode().name}catch{print("Error setting flash mode: \(String(describing: error.localizedDescription))")}}}}

给视频增加背景音乐

视频的合成,将多个音频、视频片段合成为一个视频文件。

AVComposition 可以把多个资源媒体文件,在时间上自由安排,合成想要的视频。 具体操作的就是一组音视频轨迹。 每个轨迹都是一些文件的分片,拿文件的分片自带的媒体信息、时间映射操作。 用 AVPlayer 的实例预览合成的视频 AVCompositions, 用 AVAssetExportSession 导出合成的文件。

全部代码见: https://github.com/BoxDengJZ/AVFoundation_ray

More:


最后是,关于给视频添加图形覆盖和动画。


推荐资源: AVFoundation Programming Guide 苹果文档

视频教程

大佬博客, AVPlayer 本地、网络视频播放相关

转载于:https://my.oschina.net/u/3103280/blog/2254303

AVFoundation 视频常用套路: 拍照聚焦、曝光、闪光灯,拍视频手电筒相关推荐

  1. AVFoundation 视频常用套路: 视频合成与导出,拍视频手电筒,拍照闪光灯

    拍视频,把视频文件导出到相册 - 处理 AVFoundation,套路就是配置 session, 添加输入输出, 把视频流的管道打通. 用 device 作为输入,获取信息,用 session 作为输 ...

  2. Compose使用OpenGL+CameraX快速实现相机“拍视频实时滤镜“、”拍照+滤镜“

    一.前言 短视频热潮还没有褪去,写这篇文章主要是帮助大部分人,能快速上手实现类似效果,实际上是: CameraX拿相机数据,OpenGL给CameraX提供一个Surface,数据放到OpenGL渲染 ...

  3. android图片视频图片封装,Android图片、视频资源选择库(支持图片/视频/仿微信拍照、拍视频)...

    简介 Android媒体资源选择库(支持图片/视频/仿微信拍照.拍视频),非常简单使用,支持图库多选.单选.仿微信拍照拍视频.系统照相机拍照拍视频(v1.1.2).如需使用美颜滤镜.简单图片编辑,ff ...

  4. Android最新相机(Camera)拍照、拍视频全面总结

    介绍 利用系统相机 调用系统相机拍照 获取小图标 获取全尺寸图片 添加到相册 系统相机拍视频 自定义相机 自定义相机拍照 监测设备是否有相机可使用 利用SurfaceView创建拍照时预览界面 拍照并 ...

  5. 玩 High API 系列好文:UGC内容检测、视频智能、拍照翻译、懂天气的草地喷水头...

    摘要:玩 High API 系列好文:UGC内容检测.视频智能.拍照翻译.懂天气的草地喷水头 导读:初创公司可以利用API来解决问题.了解更多场景如何玩High API?如何将API变现?请下载阿里云 ...

  6. 吃相难看!《人民日报》再评视频网站套路:消磨观众信任,必将引火烧身

    最近,IP大剧<庆余年>正在热播,不少人加入追剧行列,在各个社交网络平台上引起一波又一波的讨论,可见其受欢迎程度. 随着该剧大火,腾讯视频的骚操作也跟上来了.12月11日上午,腾讯视频官方 ...

  7. python花式编码_Python编码常用套路

    1. 循环遍历 if __name__ == '__main__':while True: 2. 录入数据 str1=raw_input()#通过split()将数据分割,并用map(int,list ...

  8. 机试题:地图定位、拍照并显示、录制视频并播放

    这两天参加面试,有个公司先出了机试题,然后才能进入下一步,机试题大意是要求实现:地图定位.拍照并显示照片.录制视频并且播放视频三个小功能. 先上我的效果图: 1.地图定位关键代码(ios8后,开启地图 ...

  9. 微信小程序-从相册获取图片,视频 使用相机拍照,录像上传+服务器(nodejs版)接收

    在本文 微信小程序-从相册获取图片 使用相机拍照 本地图片上传之前需要看看 微信小程序-获取用户session_key,openid,unionid - 后端为nodejs 代码封装是在上文添加的. ...

最新文章

  1. thinkphp整合系列之gulp实现前端自动化
  2. Linux_系统破坏性修复实验
  3. not in the sudoers file. This incident will be reported.
  4. mfc检测一个目录是否产生新文件_细数Java8中那些让人纵享丝滑的文件操作
  5. 科技+铁腕齐下 济宁市智慧环保建设成效显著
  6. Qt总结之一:遍历文件夹和文件目录,并过滤和获取文件信息、后缀名、前缀名(一)
  7. 网络编程基础概念-网络协议
  8. headless-virtualbox
  9. 数学建模实验——举重模型的matlab实现
  10. 监测资金流向原来这么简单?
  11. win10的当前桌面壁纸保存位置
  12. 在图片上加滚动文字html,如何让文字在图片上滚动
  13. 弹力弹珠java_利用java编写一个弹球小游戏
  14. windows无法连接到某个wifi_Windows无法连接到网络解决方法
  15. Linux resolv.conf 简介
  16. aar64不支持Pycharm部分版本导致cannot open local terminal的解决方法
  17. [MQ]消息队列与企业服务总线的简单比较,MQESB
  18. 微信公众号都有哪些传播方式吸引粉丝
  19. CSS系列之连续的字母或数字在Html盒子中不会自动换行,直接溢出
  20. 爆火的Java面试题-易语言线程池用法

热门文章

  1. Unity Shader法线贴图(Normal Map)及其原理
  2. mybatis通过注解使用动态sql
  3. Valve公司source引擎
  4. yolo系类问答摘抄
  5. android 08 AndroidManifest.xml
  6. 宇视网络视频录像机运动检测告警推送到手机客户端
  7. 可爱猫python_用Python制作一个可爱的猫咪小秒表
  8. UltraScale新架构FPGA中MGT参考时钟的共享问题
  9. 第四步:让主角Player发射飞镖
  10. Windows Media Player 与 ActiveMove Window遇到的相关问题