游戏电视苹果数码历史美丽
投稿投诉
美丽时装
彩妆资讯
历史明星
乐活安卓
数码常识
驾车健康
苹果问答
网络发型
电视车载
室内电影
游戏科学
音乐整形

iOS利用VideoToolbox实现视频硬解码

  需求
  本文主要将含有编码的H。264,H。265视频流文件解码为原始视频数据,解码后即可渲染到屏幕或用作其他用途。
  实现原理
  正如我们所知,编码数据仅用于传输,无法直接渲染到屏幕上,所以这里利用苹果原生框架VideoToolbox解析文件中的编码的视频流,并将压缩视频数据(h264h265)解码为指定格式(yuv,RGB)的视频原始数据,以渲染到屏幕上。
  注意:本例主要为解码,需要借助FFmpeg搭建模块,视频解析模块,渲染模块,这些模块在下面阅读前提皆有链接可直接访问。
  阅读前提音视频基础iOSFFmpeg环境搭建FFmpeg解析视频数据OpenGL渲染视频数据H。264,H。265码流结构
  代码地址:VideoDecoder
  掘金地址:VideoDecoder
  简书地址:VideoDecoder
  博客地址:VideoDecoder1、总体架构
  总体思想即将FFmpegparse到的数据装到CMBlockBuffer中,将extradata分离出的vps,sps,pps装到CMVideoFormatDesc中,将计算好的时间戳装到CMTime中,最后即可拼成完成的CMSampleBuffer以用来提供给解码器。
  CMSampleBufferCreate1。1简易流程
  FFmpegparse流程创建formatcontext:avformatalloccontext打开文件流:avformatopeninput寻找流信息:avformatfindstreaminfo获取音视频流的索引值:formatContextstreams〔i〕codecparcodectype(isVideoStream?AVMEDIATYPEVIDEO:AVMEDIATYPEAUDIO)获取音视频流:mformatContextstreams〔maudioStreamIndex〕解析音视频数据帧:avreadframe获取extradata:avbitstreamfilterfilter
  VideoToolboxdecode流程比较上一次的extradata,如果数据更新需要重新创建解码器分离并保存FFmpegparse到的extradata中分离vps,sps,pps等关键信息(比较NALU头)通过CMVideoFormatDescriptionCreateFromH264ParameterSets,CMVideoFormatDescriptionCreateFromHEVCParameterSets装载vps,sps,pps等NALUheader信息。指定解码器回调函数与解码后视频数据类型(yuv,RGB。。。)创建解码器VTDecompressionSessionCreate生成CMBlockBufferRef装载解码前数据,再将其转为CMSampleBufferRef以提供给解码器。开始解码VTDecompressionSessionDecodeFrame在回调函数中CVImageBufferRef即为解码后的数据,可转为CMSampleBufferRef传出。1。2文件结构
  image1。3快速使用初始化preview解码后的视频数据将渲染到该预览层(void)viewDidLoad{〔superviewDidLoad〕;〔selfsetupUI〕;}(void)setupUI{self。previewView〔〔XDXPreviewViewalloc〕initWithFrame:self。view。frame〕;〔self。viewaddSubview:self。previewView〕;〔self。viewbringSubviewToFront:self。startBtn〕;}解析并解码文件中视频数据(void)startDecodeByVTSessionWithIsH265Data:(BOOL)isH265{NSStringpath〔〔NSBundlemainBundle〕pathForResource:isH265?testh265:testh264ofType:MOV〕;XDXAVParseHandlerparseHandler〔〔XDXAVParseHandleralloc〕initWithPath:path〕;XDXVideoDecoderdecoder〔〔XDXVideoDecoderalloc〕init〕;decoder。delegateself;〔parseHandlerstartParseWithCompletionHandler:(BOOLisVideoFrame,BOOLisFinish,structXDXParseVideoDataInfovideoInfo,structXDXParseAudioDataInfoaudioInfo){if(isFinish){〔decoderstopDecoder〕;return;}if(isVideoFrame){〔decoderstartDecodeVideoData:videoInfo〕;}}〕;}将解码后数据渲染到屏幕上
  注意:如果数据中含有B帧则需要做一个重排序才能渲染,本例提供两个文件,一个不含B帧的h264类型文件,一个含B帧的h265类型文件。(void)getVideoDecodeDataCallback:(CMSampleBufferRef)sampleBuffer{if(self。isH265File){Note:thefirstframenotneedtosort。if(self。isDecodeFirstFrame){self。isDecodeFirstFrameNO;CVPixelBufferRefpixCMSampleBufferGetImageBuffer(sampleBuffer);〔self。previewViewdisplayPixelBuffer:pix〕;}XDXSortFrameHandlersortHandler〔〔XDXSortFrameHandleralloc〕init〕;sortHandler。delegateself;〔sortHandleraddDataToLinkList:sampleBuffer〕;}else{CVPixelBufferRefpixCMSampleBufferGetImageBuffer(sampleBuffer);〔self。previewViewdisplayPixelBuffer:pix〕;}}(void)getSortedVideoNode:(CMSampleBufferRef)sampleBuffer{int64tpts(int64t)(CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(sampleBuffer))1000);staticint64tlastpts0;NSLog(Testmariginlld,ptslastpts);lastptspts;〔self。previewViewdisplayPixelBuffer:CMSampleBufferGetImageBuffer(sampleBuffer)〕;}
  C音视频学习资料免费获取方法:关注音视频开发T哥,点击链接即可免费获取2023年最新C音视频开发进阶独家免费学习大礼包!2、具体实现2。1从Parse到的数据中检测是否需要更新extradata。
  使用FFmpegparse的数据装在XDXParseVideoDataInfo结构体中,结构体定义如下,parse模块可在上文链接中学习,本节只将解码模块。structXDXParseVideoDataInfo{uint8tdata;intdataSize;uint8textraData;intextraDataSize;Float64pts;Float64timebase;intvideoRotate;intfps;CMSampleTimingInfotimingInfo;XDXVideoEncodeFormatvideoFormat;};
  通过缓存当前extradata可以将当前获取的extradata与上一次的进行对比,如果改变需要重新创建解码器,如果没有改变则解码器可复用。(此代码尤其适用于网络流中的视频流,因为视频流可能会改变)uint8textraDatavideoInfoextraData;intsizevideoInfoextraDataSize;BOOLisNeedUpdate〔selfisNeedUpdateExtraDataWithNewExtraData:extraDatanewSize:sizelastData:lastExtraDatalastSize:lastExtraDataSize〕;。。。。。。(BOOL)isNeedUpdateExtraDataWithNewExtraData:(uint8t)newDatanewSize:(int)newSizelastData:(uint8t)lastDatalastSize:(int)lastSize{BOOLisNeedUpdateNO;if(lastSize0){isNeedUpdateYES;}else{if(lastSize!newSize){isNeedUpdateYES;}else{if(memcmp(newData,lastData,newSize)!0){isNeedUpdateYES;}}}if(isNeedUpdate){〔selfdestoryDecoder〕;lastData(uint8t)malloc(newSize);memcpy(lastData,newData,newSize);lastSizenewSize;}returnisNeedUpdate;}2。2从extradata中分离关键信息(h265:vps),sps,pps。
  创建解码器必须要有NALUHeader中的一些关键信息,如vps,sps,pps,以用来组成一个CMVideoFormatDesc描述视频信息的数据结构,如上图
  注意:h264码流需要sps,pps,h265码流则需要vps,sps,pps分离NALUHeader
  首先确定startcode的位置,通过比较前四个字节是否为00000001即可。对于h264的数据,startcode之后紧接着的是sps,pps,对于h265的数据则是vps,sps,pps确定NALUHeader长度
  通过sps索引与pps索引值可以确定sps长度,其他类似,注意,码流结构中均以4个字节的startcode作为分界符,所以需要减去对应长度。分离NALUHeader数据
  对于h264类型数据将数据上0x1F可以确定NALUheader的类型,对于h265类型数据,将数据上0x4F可以确定NALUheader的类型,这源于h264,h265的码流结构,如果不懂请参考文章最上方阅读前提中码流结构相关文章。
  得到对应类型的数据与大小后,将其赋给全局变量,即可供后面使用。if(isNeedUpdate){log4cpluserror(kModuleName,s:updateextradata,func);〔selfgetNALUInfoWithVideoFormat:videoInfovideoFormatextraData:extraDataextraDataSize:sizedecoderInfo:decoderInfo〕;}。。。。。。(void)getNALUInfoWithVideoFormat:(XDXVideoEncodeFormat)videoFormatextraData:(uint8t)extraDataextraDataSize:(int)extraDataSizedecoderInfo:(XDXDecoderInfo)decoderInfo{uint8tdataextraData;intsizeextraDataSize;intstartCodeVPSIndex0;intstartCodeSPSIndex0;intstartCodeFPPSIndex0;intstartCodeRPPSIndex0;intnalutype0;for(inti0;isize;i){if(i3){if(data〔i〕0x01data〔i1〕0x00data〔i2〕0x00data〔i3〕0x00){if(videoFormatXDXH264EncodeFormat){if(startCodeSPSIndex0){startCodeSPSIndexi;}if(istartCodeSPSIndex){startCodeFPPSIndexi;}}elseif(videoFormatXDXH265EncodeFormat){if(startCodeVPSIndex0){startCodeVPSIndexi;continue;}if(istartCodeVPSIndexstartCodeSPSIndex0){startCodeSPSIndexi;continue;}if(istartCodeSPSIndexstartCodeFPPSIndex0){startCodeFPPSIndexi;continue;}if(istartCodeFPPSIndexstartCodeRPPSIndex0){startCodeRPPSIndexi;}}}}}intspsSizestartCodeFPPSIndexstartCodeSPSIndex4;decoderInfospssizespsSize;if(videoFormatXDXH264EncodeFormat){intfppsSizesize(startCodeFPPSIndex1);decoderInfofppssizefppsSize;nalutype((uint8t)data〔startCodeSPSIndex1〕0x1F);if(nalutype0x07){uint8tspsdata〔startCodeSPSIndex1〕;〔selfcopyDataWithOriginDataRef:decoderInfospsnewData:spssize:spsSize〕;}nalutype((uint8t)data〔startCodeFPPSIndex1〕0x1F);if(nalutype0x08){uint8tppsdata〔startCodeFPPSIndex1〕;〔selfcopyDataWithOriginDataRef:decoderInfofppsnewData:ppssize:fppsSize〕;}}else{intvpsSizestartCodeSPSIndexstartCodeVPSIndex4;decoderInfovpssizevpsSize;intfppsSizestartCodeRPPSIndexstartCodeFPPSIndex4;decoderInfofppssizefppsSize;nalutype((uint8t)data〔startCodeVPSIndex1〕0x4F);if(nalutype0x40){uint8tvpsdata〔startCodeVPSIndex1〕;〔selfcopyDataWithOriginDataRef:decoderInfovpsnewData:vpssize:vpsSize〕;}nalutype((uint8t)data〔startCodeSPSIndex1〕0x4F);if(nalutype0x42){uint8tspsdata〔startCodeSPSIndex1〕;〔selfcopyDataWithOriginDataRef:decoderInfospsnewData:spssize:spsSize〕;}nalutype((uint8t)data〔startCodeFPPSIndex1〕0x4F);if(nalutype0x44){uint8tppsdata〔startCodeFPPSIndex1〕;〔selfcopyDataWithOriginDataRef:decoderInfofppsnewData:ppssize:fppsSize〕;}if(startCodeRPPSIndex0){return;}intrppsSizesize(startCodeRPPSIndex1);decoderInforppssizerppsSize;nalutype((uint8t)data〔startCodeRPPSIndex1〕0x4F);if(nalutype0x44){uint8tppsdata〔startCodeRPPSIndex1〕;〔selfcopyDataWithOriginDataRef:decoderInforppsnewData:ppssize:rppsSize〕;}}}(void)copyDataWithOriginDataRef:(uint8t)originDataRefnewData:(uint8t)newDatasize:(int)size{if(originDataRef){free(originDataRef);originDataRefNULL;}originDataRef(uint8t)malloc(size);memcpy(originDataRef,newData,size);}2。3创建解码器
  根据编码数据类型确定使用h264解码器还是h265解码器,如上图我们可得知,我们需要将数据拼成一个CMSampleBuffer类型以传给解码器解码。生成CMVideoFormatDescriptionRef
  通过(vps)sps,pps信息组成CMVideoFormatDescriptionRef。这里需要注意的是,h265编码数据有的码流数据中含有两个pps,所以在拼装时需要判断以确定参数数量。确定视频数据类型
  通过指定kCVPixelFormatType420YpCbCr8BiPlanarFullRange将视频数据类型设置为yuv420sp,如需其他格式可自行更改适配。指定回调函数创建编码器
  通过上面提供的所有信息,即可调用VTDecompressionSessionCreate生成解码器上下文对象。createdecoderif(!decoderSession){decoderSession〔selfcreateDecoderWithVideoInfo:videoInfovideoDescRef:decoderFormatDescriptionvideoFormat:kCVPixelFormatType420YpCbCr8BiPlanarFullRangelock:decoderlockcallback:VideoDecoderCallbackdecoderInfo:decoderInfo〕;}(VTDecompressionSessionRef)createDecoderWithVideoInfo:(XDXParseVideoDataInfo)videoInfovideoDescRef:(CMVideoFormatDescriptionRef)videoDescRefvideoFormat:(OSType)videoFormatlock:(pthreadmutext)lockcallback:(VTDecompressionOutputCallback)callbackdecoderInfo:(XDXDecoderInfo)decoderInfo{pthreadmutexlock(lock);OSStatusstatus;if(videoInfovideoFormatXDXH264EncodeFormat){constuint8tconstparameterSetPointers〔2〕{decoderInfo。sps,decoderInfo。fpps};constsizetparameterSetSizes〔2〕{staticcastsizet(decoderInfo。spssize),staticcastsizet(decoderInfo。fppssize)};statusCMVideoFormatDescriptionCreateFromH264ParameterSets(kCFAllocatorDefault,2,parameterSetPointers,parameterSetSizes,4,videoDescRef);}elseif(videoInfovideoFormatXDXH265EncodeFormat){if(decoderInfo。rppssize0){constuint8tconstparameterSetPointers〔3〕{decoderInfo。vps,decoderInfo。sps,decoderInfo。fpps};constsizetparameterSetSizes〔3〕{staticcastsizet(decoderInfo。vpssize),staticcastsizet(decoderInfo。spssize),staticcastsizet(decoderInfo。fppssize)};if(available(iOS11。0,)){statusCMVideoFormatDescriptionCreateFromHEVCParameterSets(kCFAllocatorDefault,3,parameterSetPointers,parameterSetSizes,4,NULL,videoDescRef);}else{status1;log4cpluserror(kModuleName,s:Systemversionistoolow!,func);}}else{constuint8tconstparameterSetPointers〔4〕{decoderInfo。vps,decoderInfo。sps,decoderInfo。fpps,decoderInfo。rpps};constsizetparameterSetSizes〔4〕{staticcastsizet(decoderInfo。vpssize),staticcastsizet(decoderInfo。spssize),staticcastsizet(decoderInfo。fppssize),staticcastsizet(decoderInfo。rppssize)};if(available(iOS11。0,)){statusCMVideoFormatDescriptionCreateFromHEVCParameterSets(kCFAllocatorDefault,4,parameterSetPointers,parameterSetSizes,4,NULL,videoDescRef);}else{status1;log4cpluserror(kModuleName,s:Systemversionistoolow!,func);}}}else{status1;}if(status!noErr){log4cpluserror(kModuleName,s:NALUheadererror!,func);pthreadmutexunlock(lock);〔selfdestoryDecoder〕;returnNULL;}uint32tpixelFormatTypevideoFormat;constvoidkeys〔〕{kCVPixelBufferPixelFormatTypeKey};constvoidvalues〔〕{CFNumberCreate(NULL,kCFNumberSInt32Type,pixelFormatType)};CFDictionaryRefattrsCFDictionaryCreate(NULL,keys,values,1,NULL,NULL);VTDecompressionOutputCallbackRecordcallBackRecord;callBackRecord。decompressionOutputCallbackcallback;callBackRecord。decompressionOutputRefCon(bridgevoid)self;VTDecompressionSessionRefsession;statusVTDecompressionSessionCreate(kCFAllocatorDefault,videoDescRef,NULL,attrs,callBackRecord,session);CFRelease(attrs);pthreadmutexunlock(lock);if(status!noErr){log4cpluserror(kModuleName,s:Createdecoderfailed,func);〔selfdestoryDecoder〕;returnNULL;}returnsession;}2。4开始解码将parse出来的原始数据装在XDXDecodeVideoInfo结构体中,以便后续扩展使用。typedefstruct{CVPixelBufferRefoutputPixelbuffer;introtate;Float64pts;intfps;intsourceindex;}XDXDecodeVideoInfo;将编码数据装在CMBlockBufferRef中。通过CMBlockBufferRef生成CMSampleBufferRef解码数据
  通过VTDecompressionSessionDecodeFrame函数即可完成解码一帧视频数据。第三个参数可以指定解码采用同步或异步方式。startdecode〔selfstartDecode:videoInfosession:decoderSessionlock:decoderlock〕;。。。。。。(void)startDecode:(XDXParseVideoDataInfo)videoInfosession:(VTDecompressionSessionRef)sessionlock:(pthreadmutext)lock{pthreadmutexlock(lock);uint8tdatavideoInfodata;intsizevideoInfodataSize;introtatevideoInfovideoRotate;CMSampleTimingInfotimingInfovideoInfotimingInfo;uint8ttempData(uint8t)malloc(size);memcpy(tempData,data,size);XDXDecodeVideoInfosourceRef(XDXDecodeVideoInfo)malloc(sizeof(XDXParseVideoDataInfo));sourceRefoutputPixelbufferNULL;sourceRefrotaterotate;sourceRefptsvideoInfopts;sourceReffpsvideoInfofps;CMBlockBufferRefblockBuffer;OSStatusstatusCMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault,(void)tempData,size,kCFAllocatorNull,NULL,0,size,0,blockBuffer);if(statuskCMBlockBufferNoErr){CMSampleBufferRefsampleBufferNULL;constsizetsampleSizeArray〔〕{staticcastsizet(size)};statusCMSampleBufferCreateReady(kCFAllocatorDefault,blockBuffer,decoderFormatDescription,1,1,timingInfo,1,sampleSizeArray,sampleBuffer);if(statuskCMBlockBufferNoErrsampleBuffer){VTDecodeFrameFlagsflagskVTDecodeFrameEnableAsynchronousDecompression;VTDecodeInfoFlagsflagOut0;OSStatusdecodeStatusVTDecompressionSessionDecodeFrame(session,sampleBuffer,flags,sourceRef,flagOut);if(decodeStatuskVTInvalidSessionErr){pthreadmutexunlock(lock);〔selfdestoryDecoder〕;if(blockBuffer)CFRelease(blockBuffer);free(tempData);tempDataNULL;CFRelease(sampleBuffer);return;}CFRelease(sampleBuffer);}}if(blockBuffer){CFRelease(blockBuffer);}free(tempData);tempDataNULL;pthreadmutexunlock(lock);}2。5解码后的数据
  解码后的数据可在回调函数中获取。这里需要将解码后的数据CVImageBufferRef转为CMSampleBufferRef。然后通过代理传出。pragmamarkCallbackstaticvoidVideoDecoderCallback(voiddecompressionOutputRefCon,voidsourceFrameRefCon,OSStatusstatus,VTDecodeInfoFlagsinfoFlags,CVImageBufferRefpixelBuffer,CMTimepresentationTimeStamp,CMTimepresentationDuration){XDXDecodeVideoInfosourceRef(XDXDecodeVideoInfo)sourceFrameRefCon;if(pixelBufferNULL){log4cpluserror(kModuleName,s:pixelbufferisNULLstatusd,func,status);if(sourceRef){free(sourceRef);}return;}XDXVideoDecoderdecoder(bridgeXDXVideoDecoder)decompressionOutputRefCon;CMSampleTimingInfosampleTime{。presentationTimeStamppresentationTimeStamp,。decodeTimeStamppresentationTimeStamp};CMSampleBufferRefsamplebuffer〔decodercreateSampleBufferFromPixelbuffer:pixelBuffervideoRotate:sourceRefrotatetimingInfo:sampleTime〕;if(samplebuffer){if(〔decoder。delegaterespondsToSelector:selector(getVideoDecodeDataCallback:)〕){〔decoder。delegategetVideoDecodeDataCallback:samplebuffer〕;}CFRelease(samplebuffer);}if(sourceRef){free(sourceRef);}}(CMSampleBufferRef)createSampleBufferFromPixelbuffer:(CVImageBufferRef)pixelBuffervideoRotate:(int)videoRotatetimingInfo:(CMSampleTimingInfo)timingInfo{if(!pixelBuffer){returnNULL;}CVPixelBufferReffinalpixelbufferpixelBuffer;CMSampleBufferRefsamplebufferNULL;CMVideoFormatDescriptionRefvideoInfoNULL;OSStatusstatusCMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault,finalpixelbuffer,videoInfo);statusCMSampleBufferCreateForImageBuffer(kCFAllocatorDefault,finalpixelbuffer,true,NULL,NULL,videoInfo,timingInfo,samplebuffer);if(videoInfo!NULL){CFRelease(videoInfo);}if(samplebufferNULLstatus!noErr){returnNULL;}returnsamplebuffer;}2。6销毁解码器
  用完后记得销毁,以便下次使用。if(decoderSession){VTDecompressionSessionWaitForAsynchronousFrames(decoderSession);VTDecompressionSessionInvalidate(decoderSession);CFRelease(decoderSession);decoderSessionNULL;}if(decoderFormatDescription){CFRelease(decoderFormatDescription);decoderFormatDescriptionNULL;}2。7补充:关于带B帧数据重排序问题
  注意,如果视频文件或视频流中含有B帧,则渲染时需要对视频帧做一个重排序,本文重点讲解码,排序将在后面文章中更新,代码中以实现,如需了解请下载Demo。
  原文链接:iOS鍒敤VideoToolbox瀹炵幇瑙嗛纭鐮绠涔

鹿晗关晓彤日本被偶遇,逛海鲜市场像老夫老妻,分手传闻不攻自破1月19日,有网友晒出了在日本海鲜市场偶遇鹿晗和关晓彤的照片,这次也是他们两人在分手传闻后的首次同框,一瞬间引发了网友的关注与热议,晒出照片的网友还表示,和鹿晗关晓彤对视了,自……PS5手柄再一次给神秘海域4合集版增色了不少农历新年前夕,《神秘海域盗贼遗产合集》登录了PS5平台。它是《神秘海域4盗贼末路》和《神秘海域失落的遗产》的合集版本,此次针对PS5的硬件性能做了优化升级,对游戏加载速度和画面……半夜总是醒,背后原因竟然是这!想要调理,做好4点很重要一觉睡到大天亮,这听起来是一件很简单的事,但对有些人来说却非常困难。因为他们不管晚上睡得多早,到了半夜总会突然醒来。尤其是凌晨一到三点,醒来之后想入睡却翻来覆去睡不着觉,……31,无解乌龙球狂轰16脚!中超保级队翻身,天津队接近上岸文彬少侃球(首发)中超第19轮比赛,天津队对阵武汉长江队,这场比赛原本很普通,并没有什么看头,但是这场比赛,天津队最终是31大胜了武汉长江队,这也引起了一些球迷的关注,特别是天……阳春三月,丽江吉北科桃花盛开,秀美乡村引客来来源:【云南日报】阳春三月,丽江石鼓镇吉北科200多亩连片的桃花竞相开放,勾勒出一幅美丽的乡村田园画卷。吉北科位于石鼓镇西南边,距镇政府14公里,四面青山环绕,优美……出门旅行,为什么带着行李箱会被人嫌弃?看完长知识随着我国的发展,旅游业已经成为了我国的潮流,大多数人都是在节假日出去旅游,其实旅游也是一种释放压力的方式,既能体验不同地区的风土人情,又能吃到大家都很向往的地方特色美食。……四年前的XSMax,大型游戏实测iphone4年前的XSMAX现在还能打吗?为什么小姐姐们拍照都喜欢用4年前的XSMax呢?最近有几个备战艺考的学妹找到我,说她们老师XSMax拍照用。这几个学妹问我,这都2023年……广西南宁建设中国东盟跨境产业融合发展合作区南宁市市长侯刚在新闻发布会上介绍相关情况。杨志雄摄中新网南宁4月18日电(记者杨志雄黄艳梅)广西南宁市市长侯刚18日表示,作为中国面向东盟开放合作的前沿和窗口,南宁市将用……天冷后,要补阳?建议男人多吃3道扶阳菜,身体壮有力气天冷后,要补阳?建议男人:多吃3道扶阳菜,身体壮有力气。哈喽,大家好。我是大厨江一舟。今天又到了和大家分享美食的时刻了,你准备好了吗?男人是家里的顶梁柱,对于80的家庭来……荆州又新增2个口袋公园快来看看你家门口有没有视频:城区又添2个口袋公园快看看你家门口有没有荆州新闻网消息(记者孙煜瑶郭佩月)推门见绿、抬头赏景、起步闻香,随着荆州中心城区口袋公园陆续建成开放,不断满足了居民的绿色获……从狸猫换太子谈宋都医馆换子案关注错换人生28年的朋友常会感叹,历史似乎真有轮回,汴京是个神奇的地方,千年前大宋皇室狸猫换太子,千年后官方医馆婴儿被调。错换人生事件似乎与戏曲存有某种不可明说的历史耦合性,故……王者S26赛季打野诸葛亮,最新出装铭文以及打法思路大家好,又见面啦,我是阿瓜,这期给大家带来一个打野篇诸葛亮的最新铭文出装,最近阿瓜自己玩诸葛亮也是嘎嘎乱杀,这个版本觉得诸葛打野才是最合适的,阿瓜也是整理了一些国服的铭文出装以……
华为平板电脑手写笔外观专利获授权笔尖部分为透明材质IT之家2月18日消息,信息显示,近日,华为技术有限公司的一项手写笔外观专利获授权。摘要显示,本外观设计产品主要用于在平板电脑上进行书写、操作,其设计要点在于产品的形状。……恩杰NZXT推出赛博朋克2077主题机箱涂鸦侧透风格,190IT之家12月11日消息,据外媒notebookcheck消息,恩杰NZXT今日推出了一款独特的《赛博朋克2077》主题限量机箱:NZXTH710i。这款产品采用侧透设计,表面……为庆祝巫师第二季剧集上线,CDPR推出定制电竞椅IT之家12月18日消息,剧集《巫师》第二季已经正式在Netflix网飞上线。为了表示庆祝,游戏公司CDPR推出了一款巫师主题电竞椅,与专业厂商Secretlab联合打造。……消息称华为MateBook笔记本新品推迟至5月份,有68个型IT之家4月10日消息,据爆料博主厂长是关同学消息,原计划4月发布的华为MateBook笔记本新品现已推迟至5月份。该博主表示,华为新款笔记本将有68个型号,有酷睿12代……OPPOK7xReno35G开启ColorOS12215AnIT之家4月7日消息,今日,OPPOK7x、Reno35G开启ColorOS12Android12升级公测招募。IT之家了解到,本次招募时间为4月7日4月8日,预约申请后……微星推出新款海皇戟3迷你主机i512400FRTX3060,IT之家6月13日消息微星现已推出新款海皇戟3迷你主机,配置升级到了12代酷睿处理器,i512400FRTX3060配置售价6199元。IT之家了解到,新款海皇戟3迷你主……五一四川累计接待游客4401。08万人次早读四川大家好!今天是5月5日,星期四,欢迎进入早读四川!每天2分钟,让你更懂四川。天气早知道早读四川导读五一四川累计接待游客4401。08万人次我省将开……25999元,机械师未来战舰III代漫威版上市水冷幻光电竞主IT之家10月15日消息,机械师未来战舰III代漫威版目前已经上市,该产品为水冷游戏台式电竞主机,拥有漫威正版授权,价格为25999元。IT之家了解到,在外观方面,该主机……机械师新品上架RTX2060电竞主机8469元,RTX306IT之家6月18日消息一年一度的618大促现已来到,国产厂商机械师拿出自家多款新品开展活动,例如全新的未来战舰II代游戏台式机电脑电竞主机,售价8569元,6期免息,晒单再返1……机械师逐空T58旗舰版游戏本618大促i711800HRTXIT之家6月17日消息机械师逐空T58旗舰版游戏本迎来618大促,该游戏本搭载了i711800H、RTX3060光追显卡、144Hz电竞屏、16G高速内存,到手价仅7599元。……雷蛇风行RGBN95空气净化面罩发布,将与沛纳海联名推出环保IT之家10月22日消息,今日雷蛇正式发布了RazerZephyr风行RGB空气净化面罩,该产品在今年早些时候已经曝光,带有双进气风扇,面罩正面透明,且具有RGB灯光。这……雷蛇发布全新PCDIY套件,涵盖电源散热器多个品类IT之家10月22日消息,今日凌晨,雷蛇在线上举行了RazerCon2021发布会,发布了全新的雷蛇PCDIY套件,囊括了电源、散热、风扇控制器等多个新品。雷蛇Kunai……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网