鸿蒙轻量设备侧Camera应用中的Surface使用

冷月星
发布于 2020-12-3 13:51
浏览
3收藏

一、总体描述


在鸿蒙轻量设备侧图形子系统中包含了Surface模块。这个模块模仿了Android的Surface实现,采用了生产者和消费者模型,但是也有些区别。
Android中的Surface的生产者和消费者模型如下:

鸿蒙轻量设备侧Camera应用中的Surface使用-鸿蒙开发者社区

HarmonyOS中Camera recoder应用中Surface模型如下:(注意本文只针对Camera应用,在AbilityMain中,Surface使用将采用远程IPC模型)

鸿蒙轻量设备侧Camera应用中的Surface使用-鸿蒙开发者社区

二、代码目录结构


Surface实现的代码路径为:foundation/graphic/lite/frameworks/surface
目录结构:

鸿蒙轻量设备侧Camera应用中的Surface使用-鸿蒙开发者社区

buffer_client_producer.cpp:实现BufferClientProducer类,支持SurfaceImpl的另外一种实现,持有这个类,可以实现远程IPC调用到WMS。在AbilityMain进程中有使用,这里不讨论。
buffer_manager.cpp:实现了BufferManager类,通过Galloc Hal来对显存进行操作(分配、释放等)。
buffer_queue_consumer.cpp:实现了BufferQueueConsumer,消费者,从BufferQueue的dirty_list拿到含有数据的buffer,使用后将buffer归还为空闲buffer。
buffer_queue.cpp:实现了BufferQueue,消费者和生产者的中介,通过BufferManager分配预先设定的buffer数组。
buffer_queue_producer.cpp:实现了BufferQueueProducer,生产者,从BufferQueue中拿到空闲的Buffer。
surface_buffer_impl.cpp:实现了SurfaceBufferImpl类,是对Galloc Hal直接分配的buffer的封装。
surface.cpp:Surface基类,实现了Surface模块的初始化。
surface_impl.cpp:实现了SurfaceImpl类,是Surface的实现类。

 

类图如下:

鸿蒙轻量设备侧Camera应用中的Surface使用-鸿蒙开发者社区

说明:
1、SurfaceImpl:是 Surface的实现类,具体实现Surface的功能。持有 BufferQueueProducer和BufferQueueConsumer。App通过SurfaceImpl可以申请SurfaceBuffer内存(生产者),也可以作为消费者请求已经有内容的SurfaceBuffer内存。
2、BufferQueueProducer:生产者类。通过持有的BufferQueue对象操作内存。
3、BufferQueueConsumer:消费者类。通过持有的BufferQueue对象操作内存。
4、BufferQueue:是生产者和消费者的中介。通过BufferManager向底层申请和释放内存。
5、BufferManager:Galloc Hal的封装。
6、SurfaceBufferImpl:是SurfaceBuffer的实现,封装了申请的内存。

 

三、代码分析


我们通过Camera录像的例子程序来分析Surface的使用过程。
Camera录像的例子程序的代码路径为:applications/sample/camera/media/camera_sample.cpp。框架代码路径:录像实现的代码路径为:foundation/multimedia/frameworks/recorder_lite。实现的功能是从Camera获取影像,通过Encoder压缩后保存到本地封装文件中,Encoder压缩的格式可以设置。
下面的时序图主要关注Camera录像过程中使用Surface的部分:

鸿蒙轻量设备侧Camera应用中的Surface使用-鸿蒙开发者社区
上面的代码主要分为三个段落:
3.1 (步骤1~14)本地Surface的初始化,设置消费者监听回调
我们从RecorderVideoSource::GetSurface()函数开始分析,代码如下:

鸿蒙轻量设备侧Camera应用中的Surface使用-鸿蒙开发者社区

在第一次调用时surface_为空,所以调用Surface::CreateSurface()静态方法构造Surface类对象。然后将本身注册为消费者监听类,也就是RecorderVideoSource::OnBufferAvailable()函数作为回调函数,这个函数会在后面分析。
我们在看下Surface::CreateSurface()静态方法:

鸿蒙轻量设备侧Camera应用中的Surface使用-鸿蒙开发者社区

实际上Surface类除了作为SurfaceImpl的基类定义了接口方法外,只实现了这一个方法,作为构造Surface实例(也就是SurfaceImpl类的对象)对象的入口。函数首先new出来SurfaceImpl对象,然后调用对象的Init方法进行初始化。我们先看看SurfaceImpl类的构造函数:

鸿蒙轻量设备侧Camera应用中的Surface使用-鸿蒙开发者社区


我们看到一共有两个构造函数,一个有参数,一个没有参数。两个构造函数主要的区别就是IsConsumer_成员的初始化不同,这个变量将影响Init()函数的走向。
下面我们来看看SurfaceImpl::Init()成员函数:

鸿蒙轻量设备侧Camera应用中的Surface使用-鸿蒙开发者社区

Init()函数首先进行BufferManager初始化,然后根据IsConsumer_走不同的流程。前面我们已经看到在Surface::CreateSurface()函数中使用了无参数构造函数,也就是IsConsumer_在构造函数中被赋值了true。这个也可以理解,因为Camera录像程序本身即是Surface的生产者也是消费者。于是下面的代码就是分别构造了 BufferQueue、BufferQueueProducer、BufferQueueConsumer对象,而且在构造BufferQueueProducer、BufferQueueConsumer对象时用BufferQueue对象作为参数,这样就形成了这样一个对象持有结构:SurfaceImpl持有BufferQueueProducer、BufferQueueConsumer对象,而BufferQueueProducer、BufferQueueConsumer对象各自持有相同的BufferQueue对象。所以可以想象SurfaceImple通过持有的Producer和Consumer对象操作Surface的申请释放,而Producer和Consumer都是通过BufferQueue对象作为中介进行数据交换。


下面我们来看看BufferManager::GetInstance()->Init()调用。BufferManager::GetInstance()代码如下:

鸿蒙轻量设备侧Camera应用中的Surface使用-鸿蒙开发者社区


上面的代码说明BufferManager采用了单例模式,也就是一个App中只有一个对象实例。
BufferManager::Init()的代码如下:

鸿蒙轻量设备侧Camera应用中的Surface使用-鸿蒙开发者社区

代码中的 grallocFucs_为 GrallocFuncs *类型,这个类型定义在display_gralloc.h(./drivers/hdf/lite/hdi/display/include/display_gralloc.h)中。

鸿蒙轻量设备侧Camera应用中的Surface使用-鸿蒙开发者社区

可以看到GrallocFuncs结构定义了一组函数指针,通过这些函数可以操作HAL层对显存进行分配释放、映射、送显等操作。


GrallocInitialize()函数调用就是获取这组函数指针。这个函数实现在display_gralloc库中,华为应该没有开源这部分代码。我们可以认为display_gralloc就是HAL层吧。


我们再回头看看RecorderVideoSource::GetSurface()函数中,在创建Surface对象后,调用了RegisterConsumerListener将本身注册为消费者监听,我们看看这个注册的过程。

鸿蒙轻量设备侧Camera应用中的Surface使用-鸿蒙开发者社区

代码中看到,最终这个消费者监听回调被保存在了BufferQueueProducer生产者对象中了。

 

3.2 (步骤15~34)生产者过程,回调消费者监听


Camera数据被Codec压缩好了后,回调到RecordAssistant::OnVencBufferAvailble()函数(这个设置的过程可以参考Camera的文章或者自己找一下):

鸿蒙轻量设备侧Camera应用中的Surface使用-鸿蒙开发者社区

这个函数大致可以分了6个步骤,我们分别描述下:

 

3.2.1 找到对应的Surface


hdl应该是codec的句柄,通过这个句柄找到保存的对应的SurfaceImpl对象指针。这里应该是一组SufaceImpl。

 

3.2.2 申请Surface内存


通过调用SurfaceImpl::RequestBuffer()来申请内存,我们看看代码:

鸿蒙轻量设备侧Camera应用中的Surface使用-鸿蒙开发者社区

producer_就是前面介绍过的BufferQueueProducer对象。

鸿蒙轻量设备侧Camera应用中的Surface使用-鸿蒙开发者社区

bufferQueue_就是Producer持有的BufferQueue对象。

鸿蒙轻量设备侧Camera应用中的Surface使用-鸿蒙开发者社区

BufferQueue::RequestBuffer()调用了CanRequest()函数去准备Buffer。

鸿蒙轻量设备侧Camera应用中的Surface使用-鸿蒙开发者社区

上面代码中 freeList_就是一组可用的内存,在第一次调用时应该是空的,需要进行分配内存操作,后续如果freeList用完了,需要再次分配。attchCount第一次调用应该是0,而queueSize就是设置的BufferQueue可以拥有的最大buffer的数量。所以第一次调用会走到NeedAttch()。

鸿蒙轻量设备侧Camera应用中的Surface使用-鸿蒙开发者社区

NeedAttch()函数红框中的代码就是调用BufferManager的AllocBuffer分配内存。分配完后增加attchCount,并且把分配的buffer放入freeList,allBuffers是所有的buffer。

鸿蒙轻量设备侧Camera应用中的Surface使用-鸿蒙开发者社区

这段代码的核心就是通过 grallocFucs_->AllocMem()函数从HAL层分配内存,然后将分配的内存用 SurfaceBufferImpl类对象封装起来,其中设置了这个内存的虚拟地址、物理地址、Usage等信息。Usage可用的值如下:

鸿蒙轻量设备侧Camera应用中的Surface使用-鸿蒙开发者社区

在这里我们使用了BUFFER_CONSUMER_USAGE_SORTWARE。


结合上面的代码,BufferQueue::RequestBuffer()函数的逻辑如下:
1、RequestBuffer()调用CanRequest()来确认是否有free buffer,如果没有就看是否还有可以分配的空间(是否queueSize分配完了),如果queueSize还没有分配完,就调用NeedAttah()分配一个buffer,并放入freeList_中。
2、在CanRequest()返回true的情况下,从freeList_拿出一个buffer,并返回上层调用者。
3、freeList_:表示当前可用的buffer list,如果为空,则在attachCount_ 小于 queueSize_的情况下再次分配一个buffer放入freeList_。
4、dirtyList_:这段代码没有涉及,这里提前介绍。表示已经被使用的buffer
5、allBuffers_:表示所有被分配的buffer,等于 freeList_ + dirtyList_ 。allBuffers的数量应该等于 attachCount_ ,并且应该小于等于 queueSize_。

 

3.2.3 得到Surface的内存地址


surfaceBuf->GetVirAddr()调用实际就是调用SurfaceBufferImpl::GetVirAddr()。这个值也就是在上面介绍的BufferManager::AllocBuffer()函数从HAL层分配内存后得到的值。VirAddr和PhyAddr对应,分别表示虚拟地址和物理地址。虚拟地址表示物理内存映射到这个进程空间的地址(通过mmap映射),而物理地址表示这块内存真实的物理地址。这里不做过多解释。

 

3.2.4 将压缩图像拷贝至Surface内存


CopyCodecOutput()这里不做过多解释,就是通过memcpy进行内存拷贝。

 

3.2.5 设置图像帧信息


从Codec压缩出来(Encode)后的每一帧图像都有pts和key两个属性。pts表示在播放时间,key表示这帧图像是否关键帧。这里不做过多解释。

 

3.2.6 调用FlushBuffer推送到BufferQueue


直接看代码:

鸿蒙轻量设备侧Camera应用中的Surface使用-鸿蒙开发者社区

producer_就是BufferQueueProducer对象。

鸿蒙轻量设备侧Camera应用中的Surface使用-鸿蒙开发者社区

这里我们的Usage是BUFFER_CONSUMER_USAGE_SORTWARE。

鸿蒙轻量设备侧Camera应用中的Surface使用-鸿蒙开发者社区

EnqueueBuffer()做了两件事情:
1、调用BufferQueue::FlushBuffer()
2、回调消费者监听
我们先看BufferQueue::FlushBuffer()函数:

鸿蒙轻量设备侧Camera应用中的Surface使用-鸿蒙开发者社区

这个函数主要就是把Buffer放入了dirtyList_数组中,等待消费者来取用。
我们在看看回调到消费者监听,我们前面介绍过监听的类是RecorderVideoSource,那么回调函数就是RecorderVideoSource::OnBufferAvailable():

鸿蒙轻量设备侧Camera应用中的Surface使用-鸿蒙开发者社区

frameAvailableCondition_的类型是std::condition_variable, notify_one()函数就是发射了一个通知,让其他线程等这个通知的地方收到,并执行下去。frameAvailableCount_表示当前需要处理的帧的数量。下面的过程介绍这个处理线程。

 

3.3 (步骤35~44)消费者过程


Recorder::RecorderImpl::StartVideoSource()函数会启动一个线程,处理从Codec压缩后的图像帧。

鸿蒙轻量设备侧Camera应用中的Surface使用-鸿蒙开发者社区

线程函数VideoSourceProcess()如下:

鸿蒙轻量设备侧Camera应用中的Surface使用-鸿蒙开发者社区

上面的线程函数分4个部分:
1、初始化和参数校验。这里不做解析了。
2、在while循环中,首先调用VideoSourceImpl的AcquireBuffer获取图像帧。
3、然后通过RecoderSink类的WirteData写入到封装文件中。这里不做分析了。有兴趣同学自己看看代码。
4、最后调用ReleaseBuffer释放帧。
我们先看看AcquireBuffer的流程:

鸿蒙轻量设备侧Camera应用中的Surface使用-鸿蒙开发者社区

上面的代码一路调用到BufferQueue::AcquireBuffer()函数,这个函数中就是从dirtyList_中拿到一个buffer。这个数据的来源就是生产者流程中放入进去的。

 

我们再看看ReleaseBuffer的流程:

鸿蒙轻量设备侧Camera应用中的Surface使用-鸿蒙开发者社区

鸿蒙轻量设备侧Camera应用中的Surface使用-鸿蒙开发者社区

鸿蒙轻量设备侧Camera应用中的Surface使用-鸿蒙开发者社区

一路调用最后到BufferQueue中的ReleaseBuffer()函数,关键部分就是把这个Buffer再次放入到freeList_列表中,后续从Producer申请内存又可以从freeList_拿走。

 

总结:
1、Camera录像程序使用的Surface是本地使用,并不是IPC到WMS中申请Surface。原因是Camera Recorder为Surface的消费者(同时也是生产者)。
2、Surface的消费者需要创建BufferQueue, BufferQueueProducer, BufferQueueConsumer 三个类。而如果只是生产者只需要创建 BufferClientProducer(远程)。
3、生产者先通过RequestBuffer从BufferQueue中请求一个可用的帧存,在帧存的填充完成后调用FlushBuffer将帧完成信息通知BufferQueue。同时回调消费者监听回调函数。
4、消费者在收到通知后通过AcquireBuffer获取已经填充完的帧存,使用完后调用ReleaseBuffer归还给BufferQueue。
5、本地Surface的创建通过Surface::CreateSurface()函数创建。远程Surface的创建通过SurfaceImpl::GenericSurfaceByIpcIo()函数,这个场景在AbilityMain进程中有使用,有兴趣的同学可以看看。

分类
标签
已于2020-12-3 13:51:03修改
3
收藏 3
回复
举报
2条回复
按时间正序
/
按时间倒序
mb603cb343e450d
mb603cb343e450d

你好,有样例或源码吗,我这边实现相机预览功能但无法拍照,原因是无法返回图片

回复
2021-3-15 17:19:40
LoinDIci
LoinDIci 回复了 mb603cb343e450d
你好,有样例或源码吗,我这边实现相机预览功能但无法拍照,原因是无法返回图片

如何实现预览功能,PreviewAssistant::YuvCopyProcess这个方法是空的

回复
2023-11-21 16:45:25
回复
    相关推荐