AMCAP程序详解:AMCeGraphBuilder
AMCAP程序详解
文章原始出处 CSDN 作者 capboy
(资料图片)
DirectShow提供了用应用程序从适当的硬件中捕捉和预览音/视频的能力。数据源包括:VCR,Camera,TVTuner,Microphone,或其他的数据源。一个应用程序可以立刻显示捕捉的数据(预览),或是保存到一个文件中。
在这个例子中,ICaptureGraphBuilder接口是处理捕捉工作的主要接口。你可以在你自己的捕捉程序中使用同样的方法和接口。在这里主要讨论ICaptureGraphBuilder如何执行音/视频的捕捉。我们假设你已经熟悉了DirectShow的filtergraph的体系和一般的capturefiltergraph的结构(可以参考DirectShow基础指南)。
ICaptureGraphBuilder接口提供了一个filtergraph builder对象,让你的应用程序在建立capture filtergraph时,省去处理很多单调乏味的工作,集中精力于捕捉中。它提供的方法满足了基本的捕捉和预览功能的要求。
FindInterface方法,在filtergraph中查找一个与捕捉有关的详细的接口。使得你可以访问一个详细接口的功能,而不需要你去列举filtergraph中的pins和filters。 RenderStream方法,连接源过滤器和渲染过滤器,选择添加一些中间的过滤器。ControlStream方法,独立地精确地控制graph的开始和结束帧。
既然是硬件捕捉,当然要和硬件打交道,接着介绍设备列举和捕捉接口。
通过ICreateDevEnum::CreateClassEnumerator方法列举捕捉系统中的设备。之后,实例化一个DirectShow的filter去使用这个设备。用ICaptureGraphBuilder::FindInterface去获得与捕捉相关的接口指针IAMDroppedFrames,IAMVideoCompression, IAMStreamConfig,以及 IAMVfwCaptureDialogs。因为设备列举和捕捉接口比较长,放在这会打乱结构,所有专门写了一篇(参考设备列举和捕捉接口)。
NOTE: 这个示例是DirectShow自带的例子。你可以在DirectShowSDK的目录Sample\DS\Caputre看这个例子代码(AMCap.cpp)。这里只是他的一些片断代码。可以说是他的中文模块的说明。AMCap例子中,把所有的接口指针和一些成员变量保存在一个全局结构gcap中了。
当不在需要保存在gcap中的接口指针是,一定要释放这些接口指针,一般是在程序的析构函数中,或是在别的同等功能函数中。如下:
if (gcap.pBuilder) gcap.pBuilder->Release(); gcap.pBuilder = NULL; if (gcap.pSink) gcap.pSink->Release(); gcap.pSink = NULL; if (gcap.pConfigAviMux) gcap.pConfigAviMux->Release(); gcap.pConfigAviMux = NULL; if (gcap.pRender) gcap.pRender->Release(); gcap.pRender = NULL; if (gcap.pVW) gcap.pVW->Release(); gcap.pVW = NULL; if (gcap.pME) gcap.pME->Release(); gcap.pME = NULL; if (gcap.pFg) gcap.pFg->Release(); gcap.pFg = NULL;
设置文件名
使用普通的OpenFileDialog获得捕捉文件的信息。通过调用AllocCaptureFile函数为捕捉文件分配空间。这一点是重要的,因为这是个巨大的空间。这样可以提高捕捉操作的速度。ICaptureGraphBuilder::AllocCapFile执行实际的文件分配,IFileSinkFilter::SetFileName指示file writerfilter使用用户选择的文件名保存数据。ICaptureGraphBuilder::SetOutputFileName把filewriter filter加入filter graph(后面会介绍,它是ICaptureGraphBuilderd自代的)。
SetCaptureFile 和 AllocCaptureFile 函数如下:
BOOL SetCaptureFile(HWND hWnd) {if(OpenFileDialog(hWnd, gcap.szCaptureFile, _MAX_PATH)) {OFSTRUCTos;//We have a capture file nameif(OpenFile(gcap.szCaptureFile, &os, OF_EXIST) ==HFILE_ERROR) {//Bring up dialog, and set new file size BOOLf = AllocCaptureFile(hWnd); if(!f) returnFALSE; } } else{returnFALSE; }SetAppCaption();// need a new app caption//Tell the file writer to use the new file name if(gcap.pSink) {WCHARwach[_MAX_PATH]; MultiByteToWideChar(CP_ACP,MB_PRECOMPOSED, gcap.szCaptureFile, -1, wach, _MAX_PATH); gcap.pSink->SetFileName(wach,NULL); } return TRUE; }// Preallocate the capturefile // BOOL AllocCaptureFile(HWND hWnd) {// We"ll get into an infinite loop in the dlgproc setting a value if (gcap.szCaptureFile[0] == 0) returnFALSE;if (DoDialog(hWnd, IDD_AllocCapFileSpace, AllocCapFileProc, 0)){//Ensure repaint after dismissing dialog before //possibly lengthy operation UpdateWindow(ghwndApp);//User has hit OK. Alloc requested capture file space BOOLf = MakeBuilder(); if(!f) returnFALSE; WCHARwach[_MAX_PATH]; MultiByteToWideChar(CP_ACP,MB_PRECOMPOSED, gcap.szCaptureFile, -1, wach, _MAX_PATH); if(gcap.pBuilder->AllocCapFile(wach,gcap.wCapFileSize* 1024L * 1024L) != NOERROR) {MessageBoxA(ghwndApp, "Error","Failed to pre-allocate capture filespace",MB_OK | MB_ICONEXCLAMATION); return FALSE; } returnTRUE; } else {returnFALSE; } }
建立GraphBuilder对象
AMCap的MakeBuilder函数建立了一个capturegraph builer对象,通过调用 CoCreateInstance获得了ICaptureGraphBuilder接口指针。AMCap把他存储到gcap结构的pBuilder中。
// Make a graph builderobject we can use for capture graph building // BOOL MakeBuilder() {//We have one already if(gcap.pBuilder) returnTRUE;HRESULThr = CoCreateInstance((REFCLSID)CLSID_CaptureGraphBuilder, NULL,CLSCTX_INPROC, (REFIID)IID_ICaptureGraphBuilder,(void**)&gcap.pBuilder); return(hr == NOERROR) ? TRUE : FALSE; }
建立Graph的渲染部分,并告诉它写文件(用先前决定的文件)
这包括一个multiplexerfilter 和 file writer。DirectShow 提供了一个AVIMUX(multiplexer)filter。在这里ICaptureGraphBuilder::SetOutputFileName是一个关键的方法。它把multiplexer 和 file writer添加到filtergraph中,连接他们,并设置文件的名字。第一个参数MEDIASUBTYPE_Avi,指出capture graph builder对象将插入一个AVI multiplexer filter,因此,filewriter将以AVI文件格式记录捕捉的数据。第二个参数(wach)是文件名。最后的两个参数指出multiplexer filter(gcap.pRender) 和file writer filter(gcap.pSink),这两个是通过SetOutputFileName函数初始化的。AMCap存储这些指针到全局结构gcap中。capture graph builder 对象建立了一个filtergraph对象(IGraphBuilder),把这两个filter加入到filter graph中去。他告诉filewriter使用指定的文件保存数据。下面的例子演示了如何调用SetOutputFileName。
// // We need a rendering section that will write the capture file outin AVI // file format //WCHAR wach[_MAX_PATH]; MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, gcap.szCaptureFile, -1,wach, _MAX_PATH); GUID guid = MEDIASUBTYPE_Avi; hr =gcap.pBuilder->SetOutputFileName(&guid,wach, &gcap.pRender,&gcap.pSink); if (hr != NOERROR) {ErrMsg("Error%x: Cannot set output file", hr); gotoSetupCaptureFail; }
获得当前的FilterGraph
因为在调用SetOutputFileName中,capturegraph builder 对象建立了一个filter graph,所以你必须把需要的filter加入同一个filter graph中。通过ICaptureGraphBuilder::GetFiltergraph获得新建立的filtergraph。返回的指针是参数gcap.pFg。
// // The graph builder created a filter graph to do that. Find outwhat it is, // and put the video capture filter in the graph too. //hr =gcap.pBuilder->GetFiltergraph(&gcap.pFg); if (hr != NOERROR) {ErrMsg("Error%x: Cannot get filtergraph", hr); gotoSetupCaptureFail; }
添加音/视频过滤器到当前的Filter Graph
hr =gcap.pFg->AddFilter(gcap.pVCap, NULL); if (hr != NOERROR) {ErrMsg("Error%x: Cannot add vidcap to filtergraph", hr); gotoSetupPreviewFail; }hr =gcap.pFg->AddFilter(gcap.pACap, NULL); if (hr != NOERROR) {ErrMsg("Error%x: Cannot add audcap to filtergraph", hr); gotoSetupCaptureFail; }
渲染视频捕捉过滤器的CapturePin和音频捕捉的Capture Pin
ICaptureGraphBuilder::RenderStream连接源过滤器的pin到渲染过滤器。pin的类别是可选的,capture pin (PIN_CATEGORY_CAPTURE) 或preview pin (PIN_CATEGORY_PREVIEW)。下面的例子演示了连接video capture filter(gcap.pVCap) 的capture pin到渲染gcap.pRender中。
// // Render the video capture and preview pins - we may not havepreview, so // don"t worry if it doesn"t work //hr =gcap.pBuilder->RenderStream(&PIN_CATEGORY_CAPTURE,NULL, gcap.pVCap, NULL, gcap.pRender); // Error checking// // Render the audio capture pin? //if (gcap.fCapAudio) {hr =gcap.pBuilder->RenderStream(&PIN_CATEGORY_CAPTURE,NULL, gcap.pACap, NULL, gcap.pRender); // Error checking
渲染Video CaptureFilter的 Preview Pin
再次调用ICaptureGraphBuilder::RenderStream,从capture filter的previewpin到video renderer。代码如下:
hr =gcap.pBuilder->RenderStream(&PIN_CATEGORY_PREVIEW,NULL, gcap.pVCap, NULL, NULL);
获得访问Video PreviewWindow的接口指针
缺省的,videopreview window是一个独立的窗口。如果你想改变默认的行为,先调用ICaptureGraphBuilder::FindInterface获得IVideoWindow接口。第二个参数通过gcap.pVCap指定,描述video capturefilter,第三个参数是想得到的接口(IVideoWindow),最后的是返回的接口。当你得到IVideoWindow接口后,你可以调用IVideoWindow的方法象put_Owner,put_WindowStyle, or SetWindowPosition 去获得video previewwindow的handle,设置窗口属性,或把他放到想要的位置。
// This will go through apossible decoder, find the video renderer it"s // connected to, and get the IVideoWindow interface onit hr =gcap.pBuilder->FindInterface(&PIN_CATEGORY_PREVIEW,gcap.pVCap, IID_IVideoWindow, (void**)&gcap.pVW); if (hr != NOERROR) {ErrMsg("Thisgraph cannot preview"); } else {RECTrc; gcap.pVW->put_Owner((long)ghwndApp);// We own the window now gcap.pVW->put_WindowStyle(WS_CHILD);// you are now a child //give the preview window all our space but where the status baris GetClientRect(ghwndApp,&rc); cyBorder= GetSystemMetrics(SM_CYBORDER); cy= statusGetHeight() + cyBorder; rc.bottom-= cy; gcap.pVW->SetWindowPosition(0,0, rc.right, rc.bottom); // be thisbig gcap.pVW->put_Visible(OATRUE); }
现在你已经建立完整的capture filtergraph了,你可以预览音频,视频,或捕捉数据。
控制 Capture FilterGraph
因为通过ICaptureGraphBuilder接口构造的capturefilter graph 只是一个简单的专门用途的filter graph,所以,控制它就象控制其他类型的filtergraph一样。你可以使用IMediaControl interface的 Run, Pause,和Stop方法,你也可以使用CBaseFilter::Pause的方法。另外ICaptureGraphBuilder 提供了ControlStream 方法去来控制 capture filter graph的流媒体的开始和结束时间。ControlStream调用IAMStreamControl::StartAt 和IAMStreamControl::StopAt控制filter graph的捕捉和预览的开始和结束的位置。
注意:不是所有的capture filter都可以,因为不是每一个capturefilter都支持IAMStreamControl。
ICaptureGraphBuilder::ControlStream方法的第一个参数(pCategory)是一个输出pin类的GUID。这个变量通常是PIN_CATEGORY_CAPTURE或 PIN_CATEGORY_PREVIEW。指定为NULL则控制所有的capture filter。第二个参数在(pFilter)指出那个filter控制。NULL说明为控制所有的filtergraph。如果只是预览(防止捕捉)的话,可以调用ICaptureGraphBuilder::ControlStream,参数用capturepin类型,MAX_TIME作为开始时间(第三个参数,pstart)。再次调用该方法,参数用previewpin类型,NULL作为开始时间则立即开始预览。第四参数指出结束的时间(pstop),含义和第三个参数一样(NULL意味着立刻)。MAX_TIME在DirectShow中定义为最大的参考时间。在这里意味着忽略或取消指定的操作。最后的参数,wStartCookie和wStopCookie分别是开始和结束的cookies(不知道该怎么翻译,因为我也不理解这个参数的含义)。下面的代码设置立刻开始预览,但是忽略捕捉。
// Let the preview sectionrun, but not the capture section // (There might not be a capture section) REFERENCE_TIME start = MAX_TIME, stop = MAX_TIME; // show us a preview first? but don"t capturequite yet... hr =gcap.pBuilder->ControlStream(&PIN_CATEGORY_PREVIEW,NULL, gcap.fWantPreview ? NULL : &start, gcap.fWantPreview ? &stop : NULL, 0, 0); if (SUCCEEDED(hr)) hr=gcap.pBuilder->ControlStream(&PIN_CATEGORY_CAPTURE,NULL, &start, NULL, 0, 0);
同样的,如果你只想要捕捉而不要预览,设置捕捉的开始时间为NULL,设置捕捉的结束时间为MAX_TIME。设置预览的开始时间为MAX_TIME,NULL为结束时间。下面的例子告诉filtergraph开始预览(第三个参数:开始时间为NULL)。结束时间指定为MAX_TIME意味着忽视停止时间(永远放下去)。 gcap.pBuilder->ControlStream(&PIN_CATEGORY_PREVIEW,NULL, NULL, MAX_TIME, 0, 0);
调用IMediaControl::Run 运行 graph
// Run the graph IMediaControl *pMC = NULL; HRESULT hr =gcap.pFg->QueryInterface(IID_IMediaControl, (void**)&pMC); if (SUCCEEDED(hr)) {hr= pMC->Run(); if(FAILED(hr)) {//Stop parts that ran pMC->Stop(); } pMC->Release(); } if (FAILED(hr)) {ErrMsg("Error%x: Cannot run preview graph", hr); returnFALSE;
如果graph已经运行,通过调用ICaptureGraphBuilder::ControlStream立刻开始捕捉。例如下面的代码,控制整个的filtergraph(第二个参数为NULL),立刻开始(第三个参数是NULL),并且永不停止(第四个参数是MAX_TIME)。
// NOW! gcap.pBuilder->ControlStream(&PIN_CATEGORY_CAPTURE,NULL, MAX_TIME, &stop, 0, 0);
停止预览或捕捉操作,调用IMediaControl::Stop,就同你调用IMediaControl::Run一样。
// Stop the graph IMediaControl *pMC = NULL; HRESULT hr =gcap.pFg->QueryInterface(IID_IMediaControl, (void**)&pMC); if (SUCCEEDED(hr)) {hr= pMC->Stop(); pMC->Release(); }
获得捕捉的信息
通过IAMDroppedFrames接口获得。测试丢失帧的数量(IAMDroppedFrames::GetNumDropped),捕捉的数量(IAMDroppedFrames::GetNumNotDropped)。IAMDroppedFrames::GetAverageFrameSize方法提供了捕捉帧的平均尺寸(单位:byte)。使用这些信息可以知道总的捕捉字节和每秒的帧数(速率)。
保存文件
最初分配的捕捉文件只是临时的保存数据,所有你可以尽可能快的捕捉。当你想把捕捉的数据保存到硬盘中时,调用ICaptureGraphBuilder::CopyCaptureFile。这个方法从先前得到的捕捉文件输出数据到你选择的另一个文件中。这个新的储存文件的大小是和实际捕捉的数据匹配的,而不是和先前的文件大小匹配。ICaptureGraphBuilder::CopyCaptureFile方法的第一个参数是复制源,第二个是目标文件。第三个参数设为TRUE指出用户允许用ESC键中断复制操作。最后参数是可选的。允许你提供一个进程指示器。如果想要的化,通过执行IAMCopyCaptureFileProgress 接口。下面示例了如何调用CopyCaptureFile。
hr =pBuilder->CopyCaptureFile(wachSrcFile,wachDstFile,TRUE,NULL);
通过普通的OpenFile Dialog得到新的文件名。用 MultiByteToWideChar函数把文件名转成宽字符(widestring),使用ICaptureGraphBuilder::CopyCaptureFile把捕捉的数据保存到指定的文件中。
BOOL SaveCaptureFile(HWND hWnd) {HRESULT hr; char achDstFile[_MAX_PATH]; WCHAR wachDstFile[_MAX_PATH]; WCHAR wachSrcFile[_MAX_PATH];if (gcap.pBuilder == NULL) returnFALSE;if (OpenFileDialog(hWnd, achDstFile, _MAX_PATH)){// We have a capture filename MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, gcap.szCaptureFile, -1,wachSrcFile, _MAX_PATH); MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, achDstFile, -1,wachDstFile, _MAX_PATH); statusUpdateStatus(ghwndStatus, "Saving capture file - pleasewait...");// We need our own graphbuilder because the main one might not exist ICaptureGraphBuilder *pBuilder; hr = CoCreateInstance((REFCLSID)CLSID_CaptureGraphBuilder, NULL, CLSCTX_INPROC, (REFIID)IID_ICaptureGraphBuilder, (void **)&pBuilder); if (hr == NOERROR) {// Allow the user to press ESC to abort...don"t ask for progress hr = pBuilder->CopyCaptureFile(wachSrcFile,wachDstFile,TRUE,NULL); pBuilder->Release(); } if (hr == S_OK) statusUpdateStatus(ghwndStatus, "Capture file saved"); else if (hr == S_FALSE) statusUpdateStatus(ghwndStatus, "Capture file save aborted"); else statusUpdateStatus(ghwndStatus, "Capture file save ERROR"); return (hr == NOERROR ? TRUE : FALSE);} else {return TRUE; // They canceled orsomething } }
关于捕捉媒体文件和获得捕捉信息的详细内容,可以参考AMCap例子的Amcap.cpp 和Status.cpp 。
标签:
相关推荐:
最新新闻:
- 热消息:全球最权威的学术期刊之一——《科学》
- 世界播报:抽象是什么?为什么学Java会遇到抽象?
- AMCAP程序详解:AMCeGraphBuilder
- 环球观察:UCB1策略和公式的理解 解决探索与利用平衡问题
- Mockplus:简洁高效的原型设计工具 轻松入门-环球快看
- java的序列化机制是什么?java序列化ID的作用:世界热闻
- rolling(k)函数的用法详解 例子说明rolling函数的用法
- 天天时讯:cdn加速测试:又拍云每个月免费提供15g流量
- 当前头条:堆与栈有什么区别?堆与栈的区别详细总结
- 什么是数据库索引?MySQL官方对索引的定义及索引优劣势分析:每日速读
- 环球要闻:Linux ora-12514多实例 ORA-12514: 错误的解决
- 组态软件是什么?组态软件在工业控制领域的应用:全球通讯
- 微信支付如何关闭“自动扣费”?关闭微信自动扣费的方法步骤 全球报资讯
- Glance详解——后台运行的服务程序
- 被喻为计算机界的诺贝尔奖——TurinSAC
- 世界信息:程序、进程和线程——多线程的创建方法
- 观速讯丨数字解谜游戏——数独直观法解题技巧
- 从高层到底层所有人的战略语言——BLD
- (java8)遍历JSONArray拼接字符串的两种方法|天天最资讯
- 与熊论道为什么解码不了?“熊”孩子都有哪些表现?_世界快看点
- 英国女子号称“现实版睡美人” 每天昏睡22小时
- 环球速递!《神领编年史》新更新上线 SE移除D加密技术
- 2023年2月PS5在英国的销量暴涨316%
- 全球消息!《死亡细胞》重返恶魔城DLC发布上市预告
- 北京最新天气预报:今天最高气温19℃,大气扩散条件和能见度较差
- Arkane确认所有版本《红霞岛》支持跨平台联机
- 太便宜了!精粤B760M主板仅需399元
- 天天微头条丨只能用功能机 司法部要求FTX创始人保释期间不能用智能手机
- 续航50短通勤 五星钻豹电动车1299元:全球热头条
- 索尼克之父中裕司认罪!承认内幕交易
- 环球视讯!微软Win11开始重新设计混音器
- 人民币是什么材料做的|即时看
- 《最终幻想16》不是基于夜光或虚幻引擎运行
- 全球讯息:《破坏领主》3月15日登陆主机 Endgame更新同时发布
- 《宝可梦:朱/紫》1.2更新后部分玩家存档被删除:环球要闻
- Wii U用户重创:任天堂下架马里奥赛车和斯普拉遁
- 烧白做法_烧白如何做
- 国外团队开发出突破性隐形眼镜 可防干眼症 速递
- 热消息:《忍者神龟:变种大乱斗》首曝中字预告 “龟”来依旧是少年
- 红楼梦十二金钗判词|环球热闻
- 当前热讯:男子用微缩模型还原高启强老家:做了11天 约400个配件
- 戴尔推出AW620M无线鼠标 DPI最高26000
- 焦点热议:百威亚太(01876):高端啤酒龙头国内市场“遇冷记”
- 外媒锐评《自杀小队》服务模式:都想学《命运》?
- 《大航海时代:起源》现已在Steam免费发售 支持中文配音
- 《古墓丽影》重启作发售10周年纪念 官方发文庆贺 新视野
- 修培刻灵“蕉仙素”是非药物疗法与现代生物科技的结晶
- 澳大利亚将对猫咪实行“宵禁” 逃跑出门或被处决
- 华语悬疑剧《模仿犯》新预告:3.31网飞独家上线:环球聚看点
- 一本院校回应招聘会有洗碗工岗位 不只面向学生
- 澳大利亚将对猫咪实行“宵禁” 逃跑出门或被处决
- 电视剧山河令演员表|环球观点
- 【当前热闻】荣耀发布四款MagicBook笔记本,主要特点一览
- 看片太爽了!极空间 Z4s NAS 售价新低:2899元
- 每日热文:CDPR发布《巫师3:狂猎》萝卜介绍 网友:不是叫葡萄吗
- 《美生中国人》制作花絮:杨紫琼、吴彦祖玩转校园_当前最新
- 《龙珠斗士Z》官方宣布仍将调整平衡性 新更新稍后实施:环球速递
- 新资讯:《火焰纹章:结合》DLC第三弹纹章士介绍影片公开
- 每日讯息!Furyu ARPG新作《恸哭机巧》OP动画公开
- 全球今头条!荣耀 MagicBook X 14/16 Pro 笔记本发布:4299 元起,13 代酷睿
- 环球热讯:京东百亿补贴正确打开方式轻松get 飞利浦小旋风空气炸锅只要349元
- 京东「百亿补贴」上线,红米K50至尊顶配低至2949元
- 抄底价!小米Redmi K50 Ultra 12+256G仅2619元|环球头条
- 折叠屏手机首次出货量下滑,同比下降 26%
- 饭制虚幻5《GTA:圣安地列斯》重制版预告:CJ前面跑 飞机后头追
- 雷军:小米造车进展超预期 预计明年上半年量产_世界球精选
- 环球热点!《如龙 维新·极》制作人采访:焕然一新的游戏体验!
- 传闻:《GTA6》2024年底推出 本体或削减内容用DLC补齐-看热讯
- 卧龙苍天陨落天柱派钥匙在哪?卧龙苍天陨落道士家的后院钥匙位置_今日热闻
- 5秒学会用抖音 但当代年轻人已不会用打印机甚至电脑:时代真变了
- “雷锋精神”在警营 伊春公安将爱“钉”入百姓家:天天快资讯
- 世界滚动:三微边设计!小米27英寸2K IPS显示器低至749元
- 环球精选!超大超爽!12G+512G旗舰机2149元
- 焦点关注:春季行情!苏宁易购举办专场供应商沟通会
- 直降500元!铭瑄MS-RTX4080 iCraft OC16G瑷珈独立显卡
- 每日快报!12代酷睿四核神U加持!天钡N-box迷你主机低至898元
- 游戏改编《龙与地下城》新预告:宝箱怪、噬脑怪亮相:今日最新
- 座位螺栓没拧紧!特斯拉在美召回3470辆汽车|焦点热闻
- 《怪物猎人崛起:曙光》推出冥渊龙和混沌黑蚀龙Q版毛绒玩具 售价196元
- 《卧龙:苍天陨落》PS5性能分析 身临其境般的体验_当前通讯
- 航空航天
- 33万买奥迪A7L 内部福利普通人别想:世界观热点
- 2022年全球光伏组件出货量top10!黑马“大明光福”首次入选_全球头条
- 京东百亿补贴3月6日全面上线 海信高清电视仅售755元
- 【环球速看料】京东百亿补贴全面上线:4899元买iPhone 14