如何使用raysource写自己的Source Filter

几篇关于如何写filter的转帖 之 ----开发source filter的source sourcestream两个基类介绍 - wqj1212 - 博客园
随笔 - 230, 文章 - 3, 评论 - 120, 引用 - 0
第一个是source,第二个是sourcestream
3.3几种常用Filter的基类 3.3.1CSource class CSource : public CBaseFilter { public: CSource(TCHAR *pName, LPUNKNOWN lpunk, CLSID clsid, HRESULT *phr); CSource(TCHAR *pName, LPUNKNOWN lpunk, CLSID clsid); #ifdef UNICODE CSource(CHAR *pName, LPUNKNOWN lpunk, CLSID clsid, HRESULT *phr); CSource(CHAR *pName, LPUNKNOWN lpunk, CLSID clsid); #endif ~CSource();
int GetPinCount(void); CBasePin *GetPin(int n);
CCritSec* pStateLock(void) { return &m_cStateL } HRESULT AddPin(CSourceStream *); HRESULT RemovePin(CSourceStream *);
STDMETHODIMP FindPin(LPCWSTR Id,IPin ** ppPin );
int FindPinNumber(IPin *iPin);
protected:
int m_iP CSourceStream **m_paS // the pins on this filter. CCritSec m_cStateL // Lock this to serialize };
CSource类是开发源Filter的基类,一个从从CSource类派生的Filter一般都支持一个或者几个从CSourceSteam类派生的输出pin。每一个输出pin都创建了一个工作线程用来将数据传递给下游的Filter。 为了生产一个输出pin,请按照如下的步骤 1 从CSoureceStream类派生一个类。 2重载CSourceStream::GetMediaType和CSourceStream::CheckMediaType方法。 3重载CBaseOutputPin::DecideBufferSize,这个方法将返回pin的buffer要求 4重载CSourceStream::FillBuffer,这个方法用来产生数据流,将数据填充到buffer中 为了派生一个源Filter,可以按照下面的步骤来做 1从CSourec类派生一个类。 2在构造函数中创建一个或者多个的输出pin。注意,这个pin要从CSourceStream派生哦。这些pin会在他们的构造函数中自动将自己添加到Filter中。并且在他们的析构函数种从Filter中删除Pin。 为了在多线程中同步Filter的状态,调用CSource::pStateLock方法,这个方法返回一个指向临界区的指针,通过CAutoLock来生成一个锁,在pin中,你可以通过CBasePin::m_pFilter来指针来操纵临界区保护你的Filter,从而达到同步。例如 CAutoLock lock(m_pFilter-&pStateLock()); 注意:CSource用来生成推模式的源Filter,如果要读文件,则应该使用拉模式。 下面我们分析一下CSourec的数据成员 int m_iP CSourceStream **m_paS CCritSec m_cStateL 总共三个数据成员,一个表示pin的数量,一个pin的数组,用来存放Filter支持的所有的pin,另外一个是临界区对象。 下面分析方法,也比较简单。 1CSource::GetPinCount 这个函数用来返回Filter支持的pin的数目。 2CSource::GetPin CBasePin *GetPin( int n); 根据指定的序号返回pin的指针。0,1,2,就是从Filter的那个pin数组中返回就是了,根据序号。 3CCritSec* pStateLock(void); 返回临界区对象 4CSource::AddPin HRESULT AddPin( CSourceStream *pStream); 构造函数通常调用这个函数将一个输出pin添加到Filter中。 5CSource::RemovePin HRESULT RemovePin( CSourceStream *pStream); 析构函数通常通过这个函数将一个输出pin从Filter中删除 6CSource::FindPinNumber int FindPinNumber( IPin *iPin); 这个函数根据指定的pin的指针,返回他的序号,如果返回-1表示没有这个pin。 7CSource::FindPin HRESULT FindPin( LPCWSTR Id, IPin **ppPin); 根据指定的ID返回pin。 注意,Filter的第一个pin ID为1开始,然后是等。
3.3.2CSourceStream class CSourceStream : public CAMThread, public CBaseOutputPin { public:
CSourceStream(TCHAR *pObjectName, HRESULT *phr, CSource *pms, LPCWSTR pName); #ifdef UNICODE CSourceStream(CHAR *pObjectName, HRESULT *phr, CSource *pms, LPCWSTR pName); #endif virtual ~CSourceStream(void); // virtual destructor ensures derived class destructors are called too.
protected:
CSource *m_pF // The parent of this stream
virtual HRESULT FillBuffer(IMediaSample *pSamp) PURE;
virtual HRESULT OnThreadCreate(void) {return NOERROR;}; virtual HRESULT OnThreadDestroy(void) {return NOERROR;}; virtual HRESULT OnThreadStartPlay(void) {return NOERROR;};
HRESULT Active(void); // Starts up the worker thread HRESULT Inactive(void); // Exits the worker thread.
public: // thread commands enum Command {CMD_INIT, CMD_PAUSE, CMD_RUN, CMD_STOP, CMD_EXIT}; HRESULT Init(void) { return CallWorker(CMD_INIT); } HRESULT Exit(void) { return CallWorker(CMD_EXIT); } HRESULT Run(void) { return CallWorker(CMD_RUN); } HRESULT Pause(void) { return CallWorker(CMD_PAUSE); } HRESULT Stop(void) { return CallWorker(CMD_STOP); }
protected: Command GetRequest(void) { return (Command) CAMThread::GetRequest(); } BOOL CheckRequest(Command *pCom) { return CAMThread::CheckRequest( (DWORD *) pCom); }
// override these if you want to add thread commands virtual DWORD ThreadProc(void); // the thread function
virtual HRESULT DoBufferProcessingLoop(void); virtual HRESULT CheckMediaType(const CMediaType *pMediaType); virtual HRESULT GetMediaType(int iPosition, CMediaType *pMediaType); virtual HRESULT GetMediaType(CMediaType *pMediaType) {return E_UNEXPECTED;}
STDMETHODIMP QueryId( LPWSTR * Id ); };
CSourceStream类用来生成一个输出pin,当然为了CSource配合。 关于如何使用这个类,可以参看CSource,这个类从CAMThread类继承而来,这个类提供了产生了数据流的工作线程,CSourceStream类通过下面的方法来给线程发送请求。 CSourceStream::Exit,CSourceStream::Init,CSourceStream::Pause,CSourceStream::Run, CSourceStream::Stop 发给线程的第一个请求一定是Init,Exit请求用来结束线程,实际上,我们没有必要亲自调用这些函数,因为在CSourceStream::Active and CSourceStream::Inactive都调用了所需要的方法。 这个类也提供几个handler方法 CSourceStream::OnThreadCreate CSourceStream::OnThreadDestroy CSourceStream::OnThreadStartPlay 首先分析一下数据成员 CSource *m_pF 靠,就一个数据成员,用来指向这个pin连接的Filter。 看看成员函数吧, 1CSourceStream::OnThreadCreate virtual HRESULT OnThreadCreate(void); 线程处理CSourceStream::ThreadProc在第一次接收到Init请求的时候,就会调用这个方法,在基类的实现中,这个函数没有作任何的事情,派生类可以在这个函数中做一些初始化线程的工作。 2CSourceStream::OnThreadDestroy 当线程退出的时候,会调用这个函数,基类的实现没有作任何的事情,你可以在派生类中做清理现场的工作。 3CSourceStream::OnThreadStartPlay 在CSourceStream::DoBufferProcessingLoop开始的时候,会调用到这个函数 4CSourceStream::Active 5CSourceStream::Inactive 6CSourceStream::GetRequest Command GetRequest(void); 这个函数可以等待下一个线程请求。这个方法重载了CAMThread::GetRequest 7CSourceStream::CheckRequest 非阻塞情况下,这个函数用来查询是否有个线程请求 8CSourceStream::ThreadProc 这个函数是工作线程的函数体,重载了CAMThread::ThreadProc方法。 virtual DWORD ThreadProc(void); 这个函数会无限的等待线程的请求,通过调用CAMThread::GetRequest方法,如果它接收到CSourceStream::Run or CSourceStream::Pause请求,它就调用 CSourceStream::DoBufferProcessingLoop方法,DoBufferProcessingLoop会一直往外推数据,直到它接收到一个CSourceStream::Stop请求,线程处理器当接收到CSourceStream::Exit请求,就会退出了。 9CSourceStream::DoBufferProcessingLoop virtual HRESULT DoBufferProcessingLoop(void); 这个方法其实是线程的实现函数,在这个循环中处理数据,并且将数据传递给下下游的Filter,每次,这个方法都会申请一个空的内存sample,然后将这个sample传递给CSourceStream::FillBuffer方法,FillBuffer方法在派生类中一定要实现哦,这个函数是用来产生数据,并将数据拷贝到sample。 当发生下面的情形时,循环停止,退出 1 当IMemInputPin::Receive方法拒绝samples。 2FillBuffer返回False,表示结束发送数据了 3线程接收到一个CSourceStream::Stop请求。 10CSourceStream::CheckMediaType virtual HRESULT CheckMediaType( const CMediaType *pMediaType); 确认是否支持指定的媒体类型。 11CSourceStream::GetMediaType virtual HRESULT GetMediaType( int iPosition, CMediaType *pMediaType);
virtual HRESULT GetMediaType( CMediaType *pMediaType); 获取指定位置的媒体类型。 12CSourceStream::Init HRESULT Init(void); 这个函数用来启动一个线程,CSourceStream::Active方法会调用这个函数的。 13CSourceStream::Exit The CSourceStream::Inactive method calls this method. 14CSourceStream::Run 15CSourceStream::Pause CSourceStream::Active会调用这个方法的。当CSourceStream::ThreadProc接收这个请求,它会调用CSourceStream::DoBufferProcessingLoop方法。 16CSourceStream::Stop The CSourceStream::Inactive method calls this method. 17CSourceStream::FillBuffer virtual HRESULT FillBuffer( IMediaSample *pSample) PURE; 最重要的一个函数。 派生类中一定要实现这个函数,媒体samples没有给这个方法提供时间戳,派生类应该调用IMediaSample::SetTime方法来设置时间戳。1)">1)">1" ng-class="{current:{{currentPage==page}}}" ng-repeat="page in pages"><li class='page' ng-if="(endIndex<li class='page next' ng-if="(currentPage
相关文章阅读自己写的file source filter 但是无法跟现有的spliter连接,媒体类型设置已经OK,为什么连接不上呢
[问题点数:100分]
自己写的file source filter 但是无法跟现有的spliter连接,媒体类型设置已经OK,为什么连接不上呢
[问题点数:100分]
不显示删除回复
显示所有回复
显示星级回复
显示得分回复
只显示楼主
相关帖子推荐:
本帖子已过去太久远了,不再提供回复功能。温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!&&|&&
前几天电脑坏了,收集的资料和一些个人总结的东西都没了,很可惜,只怪我备份不及时。想了想,还是弄个博客吧,在网上可以随时备份一下,也可以与大家交流。
LOFTER精选
IBaseFilter&*pM &&
hr&=&pBuild-&SetOutputFileName(&MEDIASUBTYPE_Avi,&&&&&&&&&&&&//Specifies&AVI&for&the&target&file &&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&L"C:\\Example.avi",&&&&&&&&&&&&&//File&name &&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&pMux,&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&//Receives&a&pointer&to&the&mux &&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&NULL);&&&&&&&//(Optional)Receives&a&pointer&to&the&file&sink&&IBaseFilter *pM
hr = pBuild-&SetOutputFileName(&MEDIASUBTYPE_Avi,
//Specifies AVI for the target file
L"C:\\Example.avi",
//File name
//Receives a pointer to the mux
//(Optional)Receives a pointer to the file sink
第一个参数表明文件的类型,这里表明是AVI,第二个参数是制定文件的名称。对于AVI文件,SetOutputFileName函数会创建一个AVI mux Filter 和一个 File writer Filter ,并且将两个filter添加到graph图中,在这个函数中,通过File Writer Filter 请求IFileSinkFilter接口,然后调用IFileSinkFilter::SetFileName方法,设置文件的名称。然后将两个filter连接起来。第三个参数返回一个指向 AVI Mux的指针,同时,它也通过第四个参数返回一个IFileSinkFilter参数,如果你不需要这个参数,你可以将这个参数设置成NULL。然后,你应该调用下面的函数将capture filter 和AVI Mux连接起来。
hr&=&pBuild-&RenderStream(&PIN_CATEGORY_CAPTURE,&&&&&&&&&//Pin&category &&
&&&&&&&&&&&&&&&&&&&&&&&&&&&MEDIATYPE_Video,&&&&&&&&&//Media&type &&
&&&&&&&&&&&&&&&&&&&&&&&&&&pCap,&&&&&&&&&//Capture&filter &&
&&&&&&&&&&&&&&&&&&&&&&&&&&NULL,&&&&&&&&&//Intermediate&filter(optional) &&
&&&&&&&&&&&&&&&&&&&&&&&&&&pMux);&&&&&&&&&//Mux&or&file&sink&filter &&
//Release&the&mux&filter &&
pMux-&Release();&&hr = pBuild-&RenderStream(&PIN_CATEGORY_CAPTURE,
//Pin category
&MEDIATYPE_Video,
//Media type
//Capture filter
//Intermediate filter(optional)
//Mux or file sink filter
//Release the mux filter
pMux-&Release();
第5个参数就是使用的上面函数返回的pMux指针。当捕捉音频的时候,媒体类型要设置为MEDIATYPE_Audio,如果你从两个不同的设备捕捉视频和音频,你最好将音频设置成主流,这样可以防止两个数据流间drift,因为avi mux filter为同步音频,会调整视频的播放速度的。为了设置master 流,调用IConfigAviMux::SetMasterStream方法,可以采用如下的代码:
IConfigAviMux&*pConfigMux&=&NULL; &&
hr&=&pMux-&QueryInterface(IID_IConfigAviMux,&(void**)&pConfigMux); &&
if(SUCCEEDED(hr)) &&
&&&&pConfigMux-&SetMasterStream(1); &&
&&&&pConfigMux-&Release(); &&
}&&IConfigAviMux *pConfigMux = NULL;
hr = pMux-&QueryInterface(IID_IConfigAviMux, (void**)&pConfigMux);
if(SUCCEEDED(hr))
pConfigMux-&SetMasterStream(1);
pConfigMux-&Release();
SetMasterStream的参数指的是数据流的数目,这个是由调用RenderStream的次序决定的。例如,如果你调用RenderStream首先用于视频流,然后是音频,那么视频流就是0,音频流就是1。添加编码filter
IBaseFilter&*pE &&
//Add&it&to&the&filter&graph &&
pGraph-&AddFilter(pEncoder,&L"Encode"); &&
//Render&the&stream &&
hr&=&pBuild-&RenderStream(&PIN_CATEGORY_CAPTURE,&&MEDIATYPE_Video,& &&
&&&&&&&&&&&&&&&&&&&&&&&&&&pCap,&pEncoder,&pMux); &&
pEncoder-&Release();&&IBaseFilter *pE
//Add it to the filter graph
pGraph-&AddFilter(pEncoder, L"Encode");
//Render the stream
hr = pBuild-&RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video,
pCap, pEncoder, pMux);
pEncoder-&Release();
2 将视频流保存成wmv格式的文件
为了将视频流保存成并编码成windows media video (WMV)格式的文件,将capture pin连到WM ASF Writer filter。构建graph图最简单的方法就是将在ICaptureGraphBuilder2::SetOutputFileName方法中指定MEDIASUBTYPE_Asf的filter。如下
IBaseFilter&*pASFWriter&=&0; &&
hr&=&pBuild-&SetOutputFileName(&MEDIASUBTYPE_Asf,&&&&&&&&//Create&a&windows&media&file &&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&L"C:\\VidCap.wmv",&&&&&&&&&&&&&&&//File&name &&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&pASFWriter,&&&&&&&&&&&&&//Receives&a&pointer&to&the&filter &&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&NULL);&&&&&&&&&&&&&&&//Receives&an&IFileSinkFilter&interface&pointer(optional)&&IBaseFilter *pASFWriter = 0;
hr = pBuild-&SetOutputFileName(&MEDIASUBTYPE_Asf,
//Create a windows media file
L"C:\\VidCap.wmv",
//File name
&pASFWriter,
//Receives a pointer to the filter
//Receives an IFileSinkFilter interface pointer(optional)
参数MEDIASUBTYPE_Asf 告诉graph builder,要使用wm asf writer作为文件接收器,于是,pbuild 就创建这个filter,将其添加到graph图中,然后调用IFileSinkFilter::SetFileName来设置输出文件的名字。第三个参数用来返回一个ASF writer指针,第四个参数用来返回文件的指针。
在将任何pin连接到WM ASF Writer之前,一定要对WM ASF Writer进行一下设置,你可以同过WM ASF Writer的IConfigAsfWriter接口指针来进行设置。
IConfigAsfWriter&*pConfig&=&0; &&
hr&=&pASFWriter-&QueryInterface(IID_IConfigAsfWriter,&(void**)&pConfig); &&
if(SUCCEEDED(hr)) &&
&&&&//Configure&the&ASF&Writer&filter &&
&&&&pConfig-&Release(); &&
}&&IConfigAsfWriter *pConfig = 0;
hr = pASFWriter-&QueryInterface(IID_IConfigAsfWriter, (void**)&pConfig);
if(SUCCEEDED(hr))
//Configure the ASF Writer filter
pConfig-&Release();
然后调用ICaptureGraphBuilder2::RenderStream将capture Filter 和 ASF writer连接起来:
hr&=&pBuild-&RenderStream(&PIN_CATEGORY_CAPTURE,&&&&&&&&&//Capture&pin &&
&&&&&&&&&&&&&&&&&&&&&&&&&&&MEDIATYPE_Video,&&&&&&&&&&&&&//Video.&Use&MEDIATYPE_Audio&for&audio &&
&&&&&&&&&&&&&&&&&&&&&&&&&&pCap,&&&&&&&&&//Pointer&to&the&capture&filter &&
&&&&&&&&&&&&&&&&&&&&&&&&&&0,& &&
&&&&&&&&&&&&&&&&&&&&&&&&&&pASFWriter);&&&&&&//Pointer&to&the&sink&filter(ASF&Filter)&&hr = pBuild-&RenderStream(&PIN_CATEGORY_CAPTURE,
//Capture pin
&MEDIATYPE_Video,
//Video. Use MEDIATYPE_Audio for audio
//Pointer to the capture filter
pASFWriter);
//Pointer to the sink filter(ASF Filter)
3保存成自定义的文件格式如果你想将文件保存成自己的格式,你必须有自己的 file writer。看下面的代码:
IBaseFilter&*pMux&=&0; &&
IFileSinkFilter&*pSink&=&0; &&
hr&=&pBuild-&SetOutputFileName(&CLSID_MyCustomMuxFilter,&&&&&//开发自己的Filter &&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&L"C:\\VidCap.avi",&&pMux,&&pSink);&&IBaseFilter *pMux = 0;
IFileSinkFilter *pSink = 0;
hr = pBuild-&SetOutputFileName(&CLSID_MyCustomMuxFilter,
//开发自己的Filter
L"C:\\VidCap.avi", &pMux, &pSink);
4如何将视频流保存进多个文件当你将视频流保存进一个文件后,如果你想开始保存第二个文件,这时,你应该首先将graph停止,然后通过IFileSinkFilter::SetFileName改变 File Writer 的文件名称。注意,IFileSinkFilter指针你可以在SetOutputFileName时通过第四个参数返回的。看看保存多个文件的代码:
IBaseFilter&*pMux&=&0; &&
IFileSinkFilter&*pSink&=&0; &&
hr&=&pBuild-&SetOutputFileName(&MEDIASUBTYPE_Avi,& &&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&L"C:\\YourFileName.avi",&&pMux,&&pSink); &&
if(SUCCEEDED(hr)) &&
&&&&hr&=&pBuild-&RenderStream(&PIN_CATEGORY_CAPTURE,&&MEDIATYPE_Video, &&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&pCap,&NULL,&pMux); &&
&&&&if(SUCCEEDED(hr)) &&
&&&&&&&&pControl-&Run(); &&
&&&&&&&&pControl-&Stop(); &&
&&&&&&&&//Change&the&file&name&and&run&the&graph&again &&
&&&&&&&&pSink-&SetFileName(L"YourFileName02.avi",&0); &&
&&&&&&&&pControl-&Run(); &&
&&&&pMux-&Release(); &&
&&&&pSink-&Release(); &&
}&&IBaseFilter *pMux = 0;
IFileSinkFilter *pSink = 0;
hr = pBuild-&SetOutputFileName(&MEDIASUBTYPE_Avi,
L"C:\\YourFileName.avi", &pMux, &pSink);
if(SUCCEEDED(hr))
hr = pBuild-&RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Video,
pCap, NULL, pMux);
if(SUCCEEDED(hr))
pControl-&Run();
pControl-&Stop();
//Change the file name and run the graph again
pSink-&SetFileName(L"YourFileName02.avi", 0);
pControl-&Run();
pMux-&Release();
pSink-&Release();
阅读(1609)|
用微信&&“扫一扫”
将文章分享到朋友圈。
用易信&&“扫一扫”
将文章分享到朋友圈。
历史上的今天
loftPermalink:'',
id:'fks_',
blogTitle:'想写一个File Filter,收集到的资料',
blogAbstract:'&来源:王冠翔的文章
实现file writer filter 收藏 Directshow中用来写文件的filter主要是file writer filter,另外在DirectX的参考代码中有一个dump的例子。由于特定的应用及PPC/Smartphone等Windows CE平台的需求,常常要自己编写file writer filter。
1. filter之间传送数据
两个filter之间是怎样传送数据在Directshow文档中有详细说明,一般都是input pin实现IMemInput接口,outpin pin调用input pin的IMemInput接口上的Receive函数。其实这就是一种推模式,一个filter graph链路中,除了source',
blogTag:'',
blogUrl:'blog/static/',
isPublished:1,
istop:false,
modifyTime:0,
publishTime:8,
permalink:'blog/static/',
commentCount:5,
mainCommentCount:1,
recommendCount:0,
bsrk:-100,
publisherId:0,
recomBlogHome:false,
currentRecomBlog:false,
attachmentsFileIds:[],
groupInfo:{},
friendstatus:'none',
followstatus:'unFollow',
pubSucc:'',
visitorProvince:'',
visitorCity:'',
visitorNewUser:false,
postAddInfo:{},
mset:'000',
remindgoodnightblog:false,
isBlackVisitor:false,
isShowYodaoAd:false,
hostIntro:'前几天电脑坏了,收集的资料和一些个人总结的东西都没了,很可惜,只怪我备份不及时。想了想,还是弄个博客吧,在网上可以随时备份一下,也可以与大家交流。',
hmcon:'1',
selfRecomBlogCount:'0',
lofter_single:''
{list a as x}
{if x.moveFrom=='wap'}
{elseif x.moveFrom=='iphone'}
{elseif x.moveFrom=='android'}
{elseif x.moveFrom=='mobile'}
${a.selfIntro|escape}{if great260}${suplement}{/if}
{list a as x}
推荐过这篇日志的人:
{list a as x}
{if !!b&&b.length>0}
他们还推荐了:
{list b as y}
转载记录:
{list d as x}
{list a as x}
{list a as x}
{list a as x}
{list a as x}
{if x_index>4}{break}{/if}
${fn2(x.publishTime,'yyyy-MM-dd HH:mm:ss')}
{list a as x}
{if !!(blogDetail.preBlogPermalink)}
{if !!(blogDetail.nextBlogPermalink)}
{list a as x}
{if defined('newslist')&&newslist.length>0}
{list newslist as x}
{if x_index>7}{break}{/if}
{list a as x}
{var first_option =}
{list x.voteDetailList as voteToOption}
{if voteToOption==1}
{if first_option==false},{/if}&&“${b[voteToOption_index]}”&&
{if (x.role!="-1") },“我是${c[x.role]}”&&{/if}
&&&&&&&&${fn1(x.voteTime)}
{if x.userName==''}{/if}
网易公司版权所有&&
{list x.l as y}
{if defined('wl')}
{list wl as x}{/list}}

我要回帖

更多关于 raysource如何下载 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信