.jpg)
一、我是怎么踩过在 OSX 上录屏的坑的
昨天开始在研究 OSX上的屏幕录制并且实时获取视频流或图像帧的实现。遇到了非常大的阻力,各种问题,昨晚纠结了一整晚,终于在小萌的启发下慢慢找到了解决办法,把谷歌和 stackoverflow都翻了个底朝天,最后的解决有点意外,中间还是有一些细节需要求证,然而除了 Apple Doc已经没有任何参考文献了,而有些机制 Apple Doc中都不会涉及。所以此刻迫不及待的想要写一篇博客,来纪念万里长征的第一步。
要实现录屏,有两种途径,一种是通过 Core Graphic,一种是通过AVFoundation。 Core Graphic的话,你可以找到苹果官方的一份 SampleCode,如果使用了
CGImageRef screenShot= CGWindowListCreateImage(CGRectMake(0.0f, 0.0f, [self screenRect].size.width, [self screenRect].size.height), kCGWindowListOptionOnScreenOnly, kCGNullWindowID, kCGWindowImageDefault|kCGWindowImageNominalResolution);
它的优点在于你可以根据 WindowID来获取指定窗口的图像,并且可以通过ListOption来设定各种包括桌面图标,去除桌面图标,去除桌面,这些七七八八的设置,所以微信 Mac端的截屏功能应该就是使用了上面这行代码。所以我们也可以设置一个 NSTimer,来按照六十分之一秒一帧的速度来获取截图,并且形成一个流。实践表明性能还不错,对于录屏这种事情烧一烧 CPU是常有的事情,毕竟你需要按帧来计算像素,而且对于 Mac而言,CPU并不是什么特别大的问题=。=因此这种办法是可行的,然而我觉得不够优雅。
同样,Core Graphic中还有一种实现办法: CGDisplayCreateImage:
CGImageRef Ref= CGDisplayCreateImage(display);
//NSData*data=(NSData*)CFBridgingRelease(CGDataProviderCopyData(CGImageGetDataProvider(Ref)));
screenImg= [[NSImage alloc] initWithCGImage:Ref size:CGDisplayScreenSize(display)];
//screenImg= [image mutableCopy];
CGImageRelease(Ref);
CGDisplayRelease(display);
这种实现的机制和上述的是一致的,实现出来的效果和性能也都不错,但是同样的还是觉得不够优雅。
所以此刻就要转向 AVFoundation了。在 AVFoundation中,有一个 input类叫做 AVCaptureScreenInput这个 input直接可以获得到当前屏幕的视频输入。这时候我想起两年前我做过视频追踪人脸的 sdk,简单地说就是通过 AVDeviceCapture来获取相机的 input然后打开一个 AVSession,然后再将 input里面的 buffer读出来,对每一帧进行人脸检测的运算。然后我按照苹果官方的一个录屏的例子和一个 Github上存在不多的这方面的仓库实现了简单的录屏,使用了AVCaptureMovieFileOutput作为 output。到这里的时候,一切都很顺利,输出到 mov文件的录屏都是正常的。然后我开始了从缓冲区读取 buffer的工作,简单来说,从缓冲区读帧是根据 AVCaptureFileOutputDelegate里面的一个回调
-(void)captureOutput:(AVCaptureFileOutput*)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection*)connection;
来实现的。这里的 CMSampleBuffers是一个 Core Foundation的对象,它包含了零个或多个压缩或未压缩过的特定媒体类型的抽样,通常被用来传递媒体数据。一个 CMSampleBuffers可以包含:
CMBlockBuffer,可能包含一个或多个的 sample(话说 sample可以翻译为帧么?还是取样的意思……)
CVImageBuffer包含了 buffer层级的附件和 sample层级的附件,还包括了包含的所有 sample的格式,大小和时间信息
按照 Apple Doc,一个 CMSampleBuffers就是这两种 buffer之一的一个 wrapper,因此每一个 CMSampleBuffers只会包含其中之一。你需要用不同的方法来取出里面的数据。所以我就很正常的按照最正常的写法来取 buffer了:
CVImageBufferRef imageBuffer= CMSampleBufferGetImageBuffer(sampleBuffer);
CVPixelBufferLockBaseAddress(imageBuffer,0);// Lock the image buffer
uint8_t*baseAddress=(uint8_t*)CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0);// Get information of the image
size_t bytesPerRow= CVPixelBufferGetBytesPerRow(imageBuffer);
size_t width= CVPixelBufferGetWidth(imageBuffer);
size_t height= CVPixelBufferGetHeight(imageBuffer);
CGColorSpaceRef colorSpace= CGColorSpaceCreateDeviceRGB();
CGContextRef newContext= CGBitmapContextCreate(baseAddress, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little| kCGImageAlphaPremultipliedFirst);
CGImageRef newImage= CGBitmapContextCreateImage(newContext);
CGContextRelease(newContext);
CGColorSpaceRelease(colorSpace);
CVPixelBufferUnlockBaseAddress(imageBuffer,0);
然而这个时候出了个小岔子,这里获取的 CMSampleBuffers里面包的是CMBlockBuffer!于是我开始查各种 stackoverflow,无解,一开始以为是视频格式的问题,需要按照 H264的编码来解析,但是怎么可能呢……百思不得其解,即使我将 CMBlockBuffer里面的 Data读取了出来,也无法转换成 NSImage,说明这个 Data不是正常的 data。那么有没有可能一帧被拆成多个 samples来传输了呢……有可能,然而我尝试了仍然无果。
这时候我回头看看,发现我这里并没有将视频导出到文件的需求,有没有其他 output来替代。偏巧我在 stackoverflow上看到了这个问题,于是就用AVCaptureVideoDataOutput来尝试。尝试之前我已经有强烈预感了--毕竟上一个 output是直接输出到文件,而这个 output明显是直接输出成 data。于是你只要这样给一个 output就可以恢复正常了:
self.output= [[AVCaptureVideoDataOutput alloc] init];
[((AVCaptureVideoDataOutput*)self.output) setVideoSettings:[NSDictionary dictionaryWithObjectsAndKeys:@(kCVPixelFormatType_32BGRA),kCVPixelBufferPixelFormatTypeKey, nil]];
dispatch_queue_t queue= dispatch_queue_create("com.sergio.chan", 0);
[(AVCaptureVideoDataOutput*)self.output setSampleBufferDelegate:self queue:queue];
这时候的 sampleBuffer已经可以正常按帧解析出来了,这里有两个问题,一个是在上面那段代码获取到一个 CGImageRef的 newImage对象后需要每一次都对 newImage进行一次release,否则内存溢出就要爆炸了,一个是线程安全问题,在上面的代码里可以看出这个新的AVCaptureVideoDataOutputSampleBufferDelegate其实是在一个独立的线程上接收回调的,因此如果你要在这个 delegate中进行 UI操作的话,记得回到主线程操作=。=
@try{
CVImageBufferRef imageBuffer= CMSampleBufferGetImageBuffer(sampleBuffer);
CVPixelBufferLockBaseAddress(imageBuffer,0);// Lock the image buffer
uint8_t*baseAddress=(uint8_t*)CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0);// Get information of the image
size_t bytesPerRow= CVPixelBufferGetBytesPerRow(imageBuffer);
size_t width= CVPixelBufferGetWidth(imageBuffer);
size_t height= CVPixelBufferGetHeight(imageBuffer);
CGColorSpaceRef colorSpace= CGColorSpaceCreateDeviceRGB();
CGContextRef newContext= CGBitmapContextCreate(baseAddress, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little| kCGImageAlphaPremultipliedFirst);
CGImageRef newImage= CGBitmapContextCreateImage(newContext);
CGContextRelease(newContext);
CGColorSpaceRelease(colorSpace);
CVPixelBufferUnlockBaseAddress(imageBuffer,0);
NSImage*image= [[NSImage alloc] initWithCGImage:newImage size:[self screenRect].size];
CGImageRelease(newImage);
dispatch_async(dispatch_get_main_queue(), ^{
if(self.imageView){
self.imageView.image= image;
}
});
}
@catch(NSException*exception){
NSLog(@"Error at%@",exception.debugDescription);
}
@finally{
return;
}
PS. Cocoa中获取 ScreenRect的方法如下:
-(NSRect)screenRect
{
NSRect screenRect;
NSArray*screenArray= [NSScreen screens];
NSScreen*screen= [screenArray objectAtIndex: 0];
screenRect= [screen frame];//[screen visibleFrame];
return screenRect;
}
这里后来又遇到一个小坑。如果使用的是 visibleFrame,那么如果你的窗口处于全屏模式,获取 visibleFrame的时候其实会把上面状态栏的那部分区域给省略了,因为计算 visibleFrame的时候估计不考虑状态栏是否隐藏吧,所以这里用 frame更好。
这里从 delegate中获取到每一帧的数据之后就可以对每一帧进行压缩,并且以 Data的形式进行传输了。差点忘记最后介绍一下 AVCaptureScreenInput的一些特性了:
self.input.capturesMouseClicks= YES;
self.input.minFrameDuration= CMTimeMake(1, 60);
self.input.scaleFactor= 0.5f;
self.input.cropRect= [self screenRect];
首先 AVCaptureScreenInput可以记录下鼠标移动的轨迹,还可以记录鼠标的点击事件(自行体验),第二个属性设置的是最大帧率,也就是60帧一秒。第三个和第四个属性顾名思义分别是缩放的比例和最后输出的裁剪区域,设置这两个属性可以减少每一帧的大小,也就是说在输入的时候就已经限制过大小了,然后你再可以进行一些压缩什么的。最后其实 AVCaptureScreenInput还有一个关键的属性,但是现在已经被废弃了,因为苹果已经把这个属性内置成系统默认了:joy:重复帧会被自动取消,这在以前的版本是可以通过一个属性设置的,现在已经被默认采用了。
多余的说几点:
其实 Core Media那层有很多知识点,但是苦于文档太少,研究的人也太少,因此实在是举步维艰,感兴趣的朋友可以参考一下苹果的 Reference看下这块的内容。
其实可能有些人知道在 AVFoundation下面, Core Media之上还有一层叫做Video ToolBox,这在2012年那会儿都是只有越狱的设备才能调用到的 Private API,但是2014年的 WWDC苹果将这一层开放出来了,因此你可以在AVFoundation更深入的层次去做视频编码解码和流处理,这块的知识我这次只看了个大概,留下了一些资料出处: Github WWDC
最后,最重要的是!代码已经整理成开源库放在 Github上了!
二、那些声优配H的时候***嗯嗯啊啊的到底是怎么配合的
有的是两个人一起录音的,有的是一个录完另一个再录。
我认为展现声音,用声音去表演,也是需要想象力的
你看过声优现场配音可能更加直观,很多声优,粉丝都认为可以直接去演真人,不一定是因为颜好,型好,他们本身也是在表演的。
有的声优配音的时候,面部表情都和动画角色一致,不能说他们是在模仿,他们是真的在演,一切动作,神态都是在辅助声音的塑造。
嘛,不过,H........蛮例外的哦,举个不太恰当的栗子,看小电影的时候会动情,看悲伤的故事会流泪,代入感嘛,还是那句话,他们也或许是把自己融进角色里吧。
而且吧,如果一部作品为H而H,观众也会乏味的,观众又不蠢。
所以很多人讨厌一部分作品删肉,因为其实H也有交心的成分啊。
某作家说,女人做着做着就会爱了,这其实可以类比到任何爱情,任何性别的人上......
好像跑题了,sorry
不过另一个网友的回答也没错吧(笑),不过我想更正,不是舍弃节操,而是舍弃矜持。
你要得到一些东西,总得付出啊,为了达到“销魂”的声音,声优们内心也很挣扎吧,没有人愿意把私密的情况公之于众的。
所以粉丝会戏谑地说“敬业”“爱岗”“业界良心”啊,这些话或许更中听。
三、CV圈里的H是什么意思
在招聘行业中,CV圈是指简历圈,也就是人才市场。CV圈里的“H”一般被用来表示“Headhunter”(猎头)的缩写。猎头是指在招聘过程中,由公司雇佣的寻找优秀人才的专业人士。H在CV圈里也表示着猎头的职业身份以及他们对企业的价值。
在职场中,猎头扮演着重要的角色。他们可以通过网络、社交媒体或者其他渠道,寻找到优秀的候选人,然后将其引荐给对应的招聘企业。猎头通过自己的行业知识、人才网络和沟通技能来协调招聘流程,从而帮助企业缩短招聘时间,避免投入大量时间和人力资源去寻找人才。
猎头的职业价值在CV圈中是无可替代的。他们为企业节省了大量的时间和资源,帮助企业聘用到优秀的人才。同时,由于猎头具有行业知识,在人才的查找和推荐过程中为公司提供更多的灵活性和选择。H的职业价值不只是体现在代替企业完成招聘过程中,更是通过他们的规划和掌握了人才市场的行情,从而提升企业人才管理能力以及招聘策略的可靠性。
四、h cv的中文全称是
HCV的中文全称是丙型肝炎病毒(Hepatitis C Virus)。
HCV是一种单股RNA病毒,是导致丙型肝炎的主要病因之一。HCV感染是由丙型肝炎病毒(HCV)感染引起的一种肝炎。HCV主要通过血液传播,例如共用针头、注射用具、血液制品、器官移植、血液透析等途径都可能会传播HCV病毒。
HCV感染的症状和严重程度各异。HCV感染的严重后果是肝硬化和肝癌等疾病的发生,这些疾病的严重程度可能会因患者感染HCV的时间长度、HCV基因型、个体免疫状态等多种因素而有所不同。而肝硬化则可能进一步发展为肝癌,HCV感染一旦发生,可能会持续多年甚至一生,导致持续性肝功能异常,甚至死亡。
HCV的临床表现多种多样,患者会有从无症状感染到急性肝炎,再到持续性肝炎、肝硬化和肝癌等不同临床表现。因此, HCV感染的诊断需要进行血液检测,以确认是否存在病毒感染。如果诊断的结果显示HCV病毒RNA阳性,说明患者已经感染了HCV病毒。
对于HCV感染的治疗,目前主要是使用抗病毒药物,例如干扰素和直接抗病毒药物等,以清除体内的HCV病毒,缓解肝脏炎症,并防止疾病进一步发展。此外,肝保护治疗、饮食调理及体育锻炼等也都是帮助患者恢复健康的重要措施。
HCV感染的预防措施
针对HCV感染的预防措施包括不分享注射用具、不通过血液途径做身体穿孔、不与感染HCV病毒的人分享个人卫生用品等方式并采取一定的生活卫生习惯和保持健康的生活方式来预防感染HCV病毒。
本文来自用户投稿,不代表币大大立场,如若转载,请注明出处:https://czxurui.com/jys/194915.html


发表回复
评论列表(0条)