Chromium为视频标签渲染视频画面的过程分析.doc_第1页
Chromium为视频标签渲染视频画面的过程分析.doc_第2页
Chromium为视频标签渲染视频画面的过程分析.doc_第3页
Chromium为视频标签渲染视频画面的过程分析.doc_第4页
Chromium为视频标签渲染视频画面的过程分析.doc_第5页
已阅读5页,还剩32页未读 继续免费阅读

Chromium为视频标签渲染视频画面的过程分析.doc.doc 免费下载

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Chromium为视频标签渲染视频画面的过程分析在浏览器中,标签与普通标签有一个显著不同点,它们的内容不是由浏览器自己绘制出来,而是由第三方组件提供的。例如,在Android平台上,标签的内容来自于系统播放器MediaPlayer的输出。然而在非全屏模式下,标签的内容又需要像普通标签一样,嵌入在HTML页面中显示,也就是由浏览器进行渲染。本文接下来就分析Chromium渲染标签内容的原理。 浏览器是否能够无缝地渲染播放器的输出,取决于播放器是否有良好的设计。一个有良好设计的播放器要有独立的输入和输出。输入就是一个URL或者一个本地文件路径,输出即为一帧一帧的视频画面。播放器都能接受URL或者本地文件路径作为输入,也就是输入这一点都能满足要求。在输出上,它的设计就很有讲究了,有上中下三种策略。 下策是让使用者提供一个窗口作为播放器的输出。这显然是不合适的,因为一般来说,播放器的使用者除了要在窗口显示视频内容之外,还需要显示其它内容,也就是需要在窗口上放其它控件。当然,如果系统支持将一个窗口作为一个控件嵌入在另外一个窗口中显示,这种设计也未尝不可,不过这种设计太不通用了。 中策是让使用者提供一个控件作为播放器的输出。这种方式可以解决下策中提出的问题。然而,有一类特殊的使用者,它们的主UI不是通过系统提供控件设计出来的,而是用自己的方式绘制出来的。例如,在浏览器中,网页中的元素就不是通过系统提供的控件显示出来的,而是用自己的图形渲染引擎绘制出来的。 上策是让使用者提供一个缓冲区作为播放器的输出。这种输出使得使用者以非常灵活的方式将视频画面显示出来。不过缺点就是使用者要多做一些工作,也就是将缓冲区的内容渲染出来的。 将播放器的输出设计为缓冲区时,有一个细节,是非常值得注意的。一般来说,播放器的输出最终要显示在屏幕上。现在流行的系统,渲染基本上都是通过GPU进行的。如果我们提供给播放器的缓冲区,是普通的缓冲区,也就是只有CPU才可以访问的缓冲区,那么使用者在使用GPU渲染的情况下,需要将缓冲区内容上传到GPU去。这就相当于是执行一个纹理上传操作。我们知道,纹理上传是一个非常慢的操作,而视频的数据又很大,分辨率通常达到1080p。因此,理想的设计是让播放器将输出写入到GPU缓冲区中去。不过,这需要系统提供支持。 好消息是Android平台提供了这样的支持。在Android系统上,SurfaceTexture描述的就是GPU缓冲区,并且以纹理的形式进行渲染。SurfaceTexture可以进一步封装在Surface中。Android系统的MediaPlayer提供了一个setSurface接口,参数是一个Surface,用来接收解码输出,也就是视频画面。这意味着Android系统的MediaPlayer支持将解码输出写入在GPU缓冲区中。这是上上策,得益于Android系统本身的良好的设计。 Chromium正是利用了SurfaceTexture作为MediaPlayer的解码输出,如图1所示:从前面文章中这个系列的文章可以知道,在Chromium的Content层,一个网页被抽象为三个Tree:CC Layer Tree、CC Pending Layer Tree和CC Active Layer Tree。其中,CC Layer Tree由Render进程中的Main线程管理,CC Pending Layer Tree和CC Active Layer Tree由Render进程中的Compositor线程管理。CC Pending Layer Tree由CC Layer Tree同步得到,CC Active Layer Tree由CC Pending Layer Tree激活得到。 Chromium为每一个标签在CC Layer Tree创建一个VideoLayer。这个VideoLayer在CC Active Layer Tree中有一个对应的VideoLayerImpl。由于网页的UI最终是通过渲染CC Active Layer Tree得到的,因此Chromium通过VideoLayerImpl接收MediaPayer的解码输出。 接下来,我们就先分析Chromium为标签在CC Layer Tree和CC Active Layer Tree中创建VideoLayer和VideoLayerImpl的过程,然后再分析MediaPlayer将解码输出交给VideoLayerImpl渲染的过程。 从前面文章中一文可以知道,当Browser进程获得要播放的视频的元数据之后,会调用WebMediaPlayerAndroid类的成员函数OnMediaMetadataChanged通知Render进程,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片void WebMediaPlayerAndroid:OnMediaMetadataChanged( const base:TimeDelta& duration, int width, int height, bool success) . if (success) OnVideoSizeChanged(width, height); . 这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。 当参数success的值等于true的时候,表示成功获取了要播放的视频的元数据,也就是长、宽和持续时间等数据。在这种情况下,WebMediaPlayerAndroid类的成员函数OnMediaMetadataChanged就会调用另外一个成员函数OnVideoSizeChanged通知要播放的视频大小发生了变化,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片void WebMediaPlayerAndroid:OnVideoSizeChanged(int width, int height) . / Lazily allocate compositing layer. if (!video_weblayer_) video_weblayer_.reset(new WebLayerImpl(cc:VideoLayer:Create(this); . . 这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。 WebMediaPlayerAndroid类的成员变量video_weblayer_描述的是标签在CC Layer Tree对应的一个Layer。如果这时候这个Layer还没有创建,那么WebMediaPlayerAndroid类的成员函数OnVideoSizeChanged就会进行创建,也就是创建一个VideoLayer对象。这个VideoLayer对象会进一步封装在一个WebLayerImpl对象,并且保存在WebMediaPlayerAndroid类的成员变量video_weblayer_中。关于WebLayerImpl,可以参考前面文章中一文。它主要是用来连接WebKit层的Graphic Layer Tree和Content层的CC Layer Tree。 接下来,我们主要关注VideoLayer对象的创建过程,也就是VideoLayer类的静态成员函数Create的实现,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片scoped_refptr VideoLayer:Create(VideoFrameProvider* provider) return make_scoped_refptr(new VideoLayer(provider); 这个函数定义在文件external/chromium_org/cc/layers/video_layer.cc中。 从前面的调用过程可以知道,参数provider指向的是一个WebMediaPlayerAndroid对象。这个WebMediaPlayerAndroid对象描述的是Render进程提供的播放器接口。VideoLayer类的静态成员函数Create使用这个WebMediaPlayerAndroid对象创建了一个VideoPlayer对象,并且返回给调用者。 VideoPlayer对象的创建过程,也就是VideoPlayer类的构造函数的实现,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片VideoLayer:VideoLayer(VideoFrameProvider* provider) : provider_(provider) DCHECK(provider_); 这个函数定义在文件external/chromium_org/cc/layers/video_layer.cc中。 VideoPlayer类的构造函数将参数provider指向的一个WebMediaPlayerAndroid对象保存在成员变量provider_,表示当前正在创建的VideoPlayer对象要渲染的内容由它提供。 从前面文章中一文可以知道,当CC Layer Tree同步为CC Pending Layer Tree的时候,CC Layer Tree中的每一个XXXLayer对象都会在CC Pending Layer Tree中有一个对应的XXXLayerImpl对象。对于标签来说,它在CC Layer Tree中对应的是一个VideoLayer对象,这个VideoLayer对象在CC Pending Layer Tree中对应的是一个VideoLayerImpl对象。这个VideoLayerImpl对象是通过调用VideoLayer类的成员函数CreateLayerImpl创建的,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片scoped_ptr VideoLayer:CreateLayerImpl(LayerTreeImpl* tree_impl) return VideoLayerImpl:Create(tree_impl, id(), provider_).PassAs(); 这个函数定义在文件external/chromium_org/cc/layers/video_layer.cc中。 参数tree_impl指向的是一个LayerTreeImpl对象。这个LayerTreeImpl对象描述的就是CC Pending Layer Tree。VideoLayer类的成员函数CreateLayerImpl使用这个LayerTreeImpl对象,以及当前正在处理的VideoLayer对象的成员变量provider_指向的一个WebMediaPlayerAndroid对象,创建一个VideoLayerImpl对象,也就是在CC Pending Layer Tree中为标签创建了一个类型为VideoLayerImpl的Layer。这是通过调用VideoLayerImpl类的静态成员函数Create实现的,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片scoped_ptr VideoLayerImpl:Create( LayerTreeImpl* tree_impl, int id, VideoFrameProvider* provider) scoped_ptr layer(new VideoLayerImpl(tree_impl, id); layer-SetProviderClientImpl(VideoFrameProviderClientImpl:Create(provider); . return layer.Pass(); 这个函数定义在文件external/chromium_org/cc/layers/video_layer_impl.cc中。 VideoPlayerImpl类的静态成员函数Create首先创建了一个VideoLayerImpl对象,接着又调用VideoFrameProviderClientImpl类的静态成员函数Create创建了一个VideoFrameProviderClientImpl对象,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片scoped_refptr VideoFrameProviderClientImpl:Create( VideoFrameProvider* provider) return make_scoped_refptr( new VideoFrameProviderClientImpl(provider); 这个函数定义在文件external/chromium_org/cc/layers/video_frame_provider_client_impl.cc中。 VideoFrameProviderClientImpl类的静态成员函数Create使用参数provider指向的一个WebMediaPlayerAndroid对象创建了一个VideoFrameProviderClientImpl对象,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片VideoFrameProviderClientImpl:VideoFrameProviderClientImpl( VideoFrameProvider* provider) : active_video_layer_(NULL), provider_(provider) . provider_-SetVideoFrameProviderClient(this); . 这个函数定义在文件external/chromium_org/cc/layers/video_frame_provider_client_impl.cc中。 VideoFrameProviderClientImpl类的构造函数除了将参数provider指向的WebMediaPlayerAndroid对象保存在成员变量provider_中,还会调用这个WebMediaPlayerAndroid对象的成员函数SetVideoFrameProviderClient,将当前正在创建的VideoFrameProviderClientImpl对象作为它的Client。这个Client将会负责接收播放器的解码输出。 WebMediaPlayerAndroid类的成员函数SetVideoFrameProviderClient的实现如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片void WebMediaPlayerAndroid:SetVideoFrameProviderClient( cc:VideoFrameProvider:Client* client) . video_frame_provider_client_ = client; 这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。 WebMediaPlayerAndroid类的成员函数SetVideoFrameProviderClient主要是将参数client指向的一个VideoFrameProviderClientImpl对象保存在成员变量video_frame_provider_client_中。 回到前面分析的VideoLayerImpl类的静态成员函数Create中,它创建了一个VideoFrameProviderClientImpl对象之后,接下来会将这个VideoFrameProviderClientImpl对象设置给前面创建的VideoLayerImpl对象。这是通过调用VideoLayerImpl类的成员函数SetProviderClientImpl实现的,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片void VideoLayerImpl:SetProviderClientImpl( scoped_refptr provider_client_impl) provider_client_impl_ = provider_client_impl; 这个函数定义在文件external/chromium_org/cc/layers/video_layer_impl.cc中。 VideoLayerImpl类的成员函数SetProviderClientImpl将参数provider_client_impl指向的一个VideoFrameProviderClientImpl对象保存在成员变量provider_client_impl_中。 这一步执行完成之后,Chromium就为标签在CC Pending Layer Tree中创建了一个VideoLayerImpl对象。从前面文章中一文可以知道,这个VideoLayerImpl对象在CC Pending Layer Tree激活为CC Active Layer Tree的时候,会变成CC Active Layer Tree中的一个节点。 这样,Chromium就为标签在CC Active Layer Tree中创建了一个类型为VideoLayerImpl的Layer。接下来我们继续分析Chromium创建SurfaceTexture接收MediaPlayer的解码输出的过程。 从前面文章中一文可以知道,获得了标签要播放的视频的元数据之后,WebKit层就会请求Content层启动它的播放器对视频进行播放,也就是调用WebMediaPlayerAndroid类的成员函数play对视频进行播放,它的实现如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片void WebMediaPlayerAndroid:play() . TryCreateStreamTextureProxyIfNeeded(); . if (hasVideo() & needs_establish_peer_ & !player_manager_-IsInFullscreen(frame_) EstablishSurfaceTexturePeer(); if (paused() player_manager_-Start(player_id_); . 这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。 WebMediaPlayerAndroid类的成员函数play的详细分析可以参考前面文章中一文。这里我们主要它关注它调用的另外两个成员函数TryCreateStreamTextureProxyIfNeeded和EstablishSurfaceTexturePeer的实现。其中,前者用来创建一个SurfaceTexture,后者用来将创建出来的SurfaceTexture封装成一个Surface,并且设置为MediaPlayer的解码输出。 WebMediaPlayerAndroid类的成员函数TryCreateStreamTextureProxyIfNeeded的实现如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片void WebMediaPlayerAndroid:TryCreateStreamTextureProxyIfNeeded() / Already created. if (stream_texture_proxy_) return; . stream_texture_proxy_.reset(stream_texture_factory_-CreateProxy(); if (stream_texture_proxy_) DoCreateStreamTexture(); ReallocateVideoFrame(); if (video_frame_provider_client_) stream_texture_proxy_-BindToLoop( stream_id_, video_frame_provider_client_, compositor_loop_); 这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。 WebMediaPlayerAndroid类的成员变量stream_texture_proxy_指向的是一个StreamTextureProxyImpl对象。这个StreamTextureProxyImpl对象用来在Compositor线程中接收MediaPlayer的解码输出。 WebMediaPlayerAndroid类的成员函数TryCreateStreamTextureProxyIfNeeded首先检查成员变量stream_texture_proxy_是否已经指向了一个StreamTextureProxyImpl对象。如果已经指向,那么就说明Chromium已经为当前正在处理的WebMediaPlayerAndroid创建过了一个SurfaceTexture。在这种情况下,WebMediaPlayerAndroid类的成员函数TryCreateStreamTextureProxyIfNeeded就什么也不做就返回。 另一方面,如果WebMediaPlayerAndroid类的成员变量stream_texture_proxy_还没有指向一个StreamTextureProxyImpl对象,那么WebMediaPlayerAndroid类的成员函数TryCreateStreamTextureProxyIfNeeded就会调用另外一个成员变量stream_texture_factory_指向的一个StreamTextureFactoryImpl对象的成员函数CreateProxy创建一个StreamTextureProxyImpl对象,并且保存在成员变量stream_texture_proxy_中。 WebMediaPlayerAndroid类的成员变量stream_texture_factory_指向的StreamTextureFactoryImpl对象的创建过程可以参考前面文章中一文,它内部包含有一个GpuChannelHost对象。这个GpuChannelHost对象描述的是一个连接到GPU进程的GPU通道。 StreamTextureFactoryImpl类的成员函数CreateProxy的实现如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片StreamTextureProxy* StreamTextureFactoryImpl:CreateProxy() DCHECK(channel_.get(); StreamTextureHost* host = new StreamTextureHost(channel_.get(); return new StreamTextureProxyImpl(host); 这个函数定义在文件external/chromium_org/content/renderer/media/android/stream_texture_factory_impl.cc中。 StreamTextureFactoryImpl类的成员变量channel_描述的就是上述的GPU通道。StreamTextureFactoryImpl类的成员函数CreateProxy首先使用这个GPU通道创建一个StreamTextureHost对象,然后再将这个StreamTextureHost对象封装在一个StreamTextureProxyImpl对象中返回给调用者。这个StreamTextureHost对象描述的实际上就是Render进程接下来要求GPU进程创建的SurfaceTexture。 回到WebMediaPlayerAndroid类的成员函数TryCreateStreamTextureProxyIfNeeded中,它创建了一个StreamTextureProxyImpl对象之后,紧接着又会调用另外一个成员函数DoCreateStreamTexture请求GPU进程创建一个SurfaceTexture,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片void WebMediaPlayerAndroid:DoCreateStreamTexture() . stream_id_ = stream_texture_factory_-CreateStreamTexture( kGLTextureExternalOES, &texture_id_, &texture_mailbox_); 这个函数定义在文件external/chromium_org/content/renderer/media/android/webmediaplayer_android.cc中。 WebMediaPlayerAndroid类的成员函数DoCreateStreamTexture调用成员变量stream_texture_factory_指向的StreamTextureFactoryImpl对象的成员函数CreateStreamTexture请求GPU进程创建一个SurfaceTexture对象,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片unsigned StreamTextureFactoryImpl:CreateStreamTexture( unsigned texture_target, unsigned* texture_id, gpu:Mailbox* texture_mailbox) GLuint stream_id = 0; gpu:gles2:GLES2Interface* gl = context_provider_-ContextGL(); gl-GenTextures(1, texture_id); stream_id = gl-CreateStreamTextureCHROMIUM(*texture_id); gl-GenMailboxCHROMIUM(texture_mailbox-name); gl-BindTexture(texture_target, *texture_id); gl-ProduceTextureCHROMIUM(texture_target, texture_mailbox-name); return stream_id; 这个函数定义在文件external/chromium_org/content/renderer/media/android/stream_texture_factory_impl.cc中。 在Android中,创建一个SurfaceTexture对象,需要指定一个纹理ID。因此,StreamTextureFactoryImpl类的成员函数CreateStreamTexture在请求GPU进程创建SurfaceTexture对象之前,首先会创建一个纹理。这个纹理可以通过调用Chromium为Render进程提供的Command Buffer OpenGL接口的成员函数GenTextures创建。 StreamTextureFactoryImpl类的成员变量context_provider_指向的是一个ContextProviderCommandBuffer对象。调用这个ContextProviderCommandBuffer对象的成员函数ContextGL可以获得一个Command Buffer GL接口。当我们调用这个Command Buffer GL接口的成员函数执行GPU命令时,它实际上是通过Command Buffer将GPU命令发送给GPU进程执行。关于Command Buffer GL的更多知识,可以参考前面文章中这个系列的文章。 获得了一个纹理ID之后,StreamTextureFactoryImpl类的成员函数CreateStreamTexture就继续调用上述Command Buffer GL接口的成员函数CreateStreamTextureCHROMIUM请求GPU进程创建一个SurfaceTexutre对象。 创建了SurfaceTexutre对象之后,StreamTextureFactoryImpl类的成员函数CreateStreamTexture还会为前面创建出来的纹理创建一个Mailbox。这个Mailbox的作用是将与它关联的纹理发送给Browser进程进行合成,以便显示在浏览器窗口中。这个纹理描述的实际上就是MediaPlayer的解码输出,因此,上述Mailbox是用来将MediaPlayer的解码输出交给Browser进程合成显示在浏览器窗口中。关于Mailbox的更多知识,可以参考前面文章中一文。 接下来我们继续分析Render进程请求GPU进程创建SurfaceTexture对象的过程,也就是Command Buffer GL接口的成员函数CreateStreamTextureCHROMIUM的实现。 从前面文章中这个系列的文章可以知道,Chromium是通过GLES2Implementation类来描述Command Buffer GL接口的,因此接下来我们分析GLES2Implementation类的成员函数CreateStreamTextureCHROMIUM的实现,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片GLuint GLES2Implementation:CreateStreamTextureCHROMIUM(GLuint texture) . return gpu_control_-CreateStreamTexture(texture); 这个函数定义在文件external/chromium_org/gpu/command_buffer/client/gles2_implementation.cc中。 从前面文章中一文可以知道,GLES2Implementation类的成员变量gpu_control_指向的是一个CommandBufferProxyImpl对象。GLES2Implementation类的成员函数CreateStreamTextureCHROMIUM调用这个CommandBufferProxyImpl对象的成员函数CreateStreamTexture请求GPU进程创建一个SurfaceTexture对象,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片uint32 CommandBufferProxyImpl:CreateStreamTexture(uint32 texture_id) . int32 stream_id = channel_-GenerateRouteID(); bool succeeded; Send(new GpuCommandBufferMsg_CreateStreamTexture( route_id_, texture_id, stream_id, &succeeded); . return stream_id; 这个函数定义在文件external/chromium_org/content/common/gpu/client/command_buffer_proxy_impl.cc中。 CommandBufferProxyImpl类的成员函数CreateStreamTexture向GPU进程发送一个类型为GpuCommandBufferMsg_CreateStreamTexture的IPC消息,用来请求创建一个SurfaceTexture对象。 GPU进程通过GpuCommandBufferStub类的成员函数OnMessageReceived接收类型为GpuCommandBufferMsg_CreateStreamTexture的IPC消息,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片bool GpuCommandBufferStub:OnMessageReceived(const IPC:Message& message) . bool handled = true; IPC_BEGIN_MESSAGE_MAP(GpuCommandBufferStub, message) . IPC_MESSAGE_HANDLER(GpuCommandBufferMsg_CreateStreamTexture, OnCreateStreamTexture) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() . return handled; 这个函数定义在文件external/chromium_org/content/common/gpu/gpu_command_buffer_stub.cc中。 GpuCommandBufferStub类的成员函数OnMessageReceived将类型为GpuCommandBufferMsg_CreateStreamTexture的IPC消息分发给另外一个成员函数OnCreateStreamTexture处理,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片void GpuCommandBufferStub:OnCreateStreamTexture( uint32 texture_id, int32 stream_id, bool* succeeded) #if defined(OS_ANDROID) *succeeded = StreamTexture:Create(this, texture_id, stream_id); #else *succeeded = false; #endif 这个函数定义在文件external/chromium_org/content/common/gpu/gpu_command_buffer_stub.cc中。 SurfaceTexture是Android平台特有的接口,因此GpuCommandBufferStub类的成员函数OnCreateStreamTexture只有在Android平台上才会响应请求创建一个SurfaceTexture对象。 这个SurfaceTexture对象是通过调用StreamTexture类的静态成员函数Create创建的,如下所示:cpp view plain copy 在CODE上查看代码片派生到我的代码片bool StreamTexture:Create( GpuCommandBufferStub* owner_stub, uint32 client_texture_id, int stream_id) GLES2Decoder* decoder = owner_stub-decoder(); TextureManager* texture_manager = decoder-GetContextGroup()-texture_manager(); TextureRef* texture = texture_manager-GetTexture(client_texture_id); if (texture & (!texture-texture()-target() | texture-texture()-target() = GL_TEXTURE_EXTERNAL_OES) / TODO: Ideally a valid image id was returned to the client so that / it could then call glBindTexImage2D() for doing the following. scoped_refptr gl_image( new StreamTexture(owner_stub, stream_id, texture-service_id(); gfx:

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论