Rasterer 2015-02-26T08:37:36+00:00 kejinlu@gmail.com Smplayer crash问题与Unicode Bidi算法 2015-02-26T00:00:00+00:00 Rasterer http://rasterer.github.io/2015/02/smplayer-crash问题与unicode-bidi算法 今天在Gentoo上装好smplayer之后,发现不能使用,一打开视频就报错:

mplayer has finished unexpectedly exit code 1

但直接在命令行里敲命令却没有问题:

mplayer kauai_1080p_MPEG4_AVC_H.264_AAC_new.mp4

后来发现是smplayer调用mplayer中的一个选项有问题: -noflip-hebrew ,把这个选项去掉就OK了。接触过《圣经》的人肯定知道Hebrew是希伯来语的意思。 又看到在原始的log中有句:

MPlayer was compiled without FriBiDi support.

查了一下,知道FriBiDi是Unicode Bidirectional Algorithm (bidi)的开源实现。不同的语言有不同的书写方式,大部分从左往右书写,也有个别语言——如阿拉伯语和希伯来语,从右往左书写。某些时候,这两种书写方式可能混合存在。因此需要有一个”人“把文字的显示方向告诉计算机底层硬件。Bidi算法就是做这个事情的。

查到这里,smplayer的问题已经迎刃而解,只需要为mplayer添加一个USE.

media-video/mplayer bidi

不过由此引出的关于文字显示的问题还是很有趣的。这个一般在web前端领域会涉及的比较多。特别是我们中国的古籍都是从上往下,从右往左书写,大家有兴趣可以去了解一下如何将这种方式在计算机上显示出来。

]]>
Android中资源的handle——file descriptor 2015-02-26T00:00:00+00:00 Rasterer http://rasterer.github.io/2015/02/android中资源的handle-file-descriptor Android系统中有很多资源需要跨进程共享,比如说BufferQueue中的buffer,以及与之一一对应的Fence。共享Buffer的时候,肯定不可能将Buffer内容复制一遍到对方进程,其开销不是系统能承受的,最合理的应该是利用MMU的特性,就可以轻易地将同一块buffer映射到不同的进程地址空间。我们在从Gralloc模块分配出来buffer后最先拿到的是它的handle,通过handle能唯一地找到这块buffer,即这个handle和这块buffer一一对应。然后我们可以把这个handle跨进程传输给另外的进程,那些进程可以利用该handle将buffer映射到自己的地址空间。Android中定义的native_hanle_t:

typedef struct native_handle
{
    int version;        /* sizeof(native_handle_t) */
    int numFds;         /* number of file-descriptors at &data[0] */
    int numInts;        /* number of ints at &data[numFds] */
    int data[0];        /* numFds + numInts ints */
} native_handle_t;

显然,拷贝一个handle比拷贝整个buffer的开销要小得多。

从结构体定义中可以看到,既可以用file descriptors(FD)来表示资源,也可以用系统中唯一的一个整数(ID)来标识资源。比如说,可以用ID来表示buffer,用FD表示Fence,合起来组成一个handle。要着重说明的是使用FD的方法。为此,Android的Binder传输框架特意设计了一种传输类型——文件描述符(其他四种是:强/弱Binder,强/弱引用)。其基本实现也很简单:FD在进入驱动层传输的时候转化为一个struct file结构体指针,接收进程中调用get_unused_fd()拿到一个空闲的fd,然后将该指针放到自己的file table中。于是,两个进程中各自的FD可以索引到同一个file结构体,通过file结构体的私有指针就可以找到具体的资源了。

用ID的方法则简单得多,因为ID是全局的,所以在一个进程中如果某块buffer的ID是5,在另一个进程自然也是5。接触过linux图形驱动的人可能知道,这种方法在GEM——Graphics Execution Manager中有个特定的名字叫做——Flink,而用FD的方法则称之为——Prime。当前linux桌面上的DRI2框架用就是Flink方法。

那这两种方法有什么区别呢?最重要的一点是安全性。如果用全局ID的话,第三方进程可以投石问路,随意猜测一个ID,来试探系统中的资源,可以比较轻易的窥视甚至改写某个资源的内容。而用FD的方法就安全得多,因为FD的传输是由比较严格的系统编程接口控制。如果没有申请共享资源,就随意猜测FD,只会让内核抱怨:在当前进程没有与该FD对应的打开文件!所以对于一些重要的资源,我们应该偏向于用FD来标识它们。

]]>
OpenGL驱动的异步性 2014-10-14T00:00:00+00:00 Rasterer http://rasterer.github.io/2014/10/OpenGL驱动的异步性 Sync or Async

GPU虽然是以并行计算出名,但其更基础的架构仍然是一套pipeline。与CPU类似,GPU也拥有很长的pipeline, 但基本上可以划分为 两个大的阶段:几何(Geometry)和光栅化(Rasterization)。使用Pipeline的优点是,可以将一个大的工作,分成好几个阶段 每个阶段可以同时执行。理论上N级的流水线能将效率最大提升N倍。例如,当一部分图元在进行光栅化操作时,一些新的图元已经开始进入 几何阶段进行处理。从而保证pipeline的各个阶段都处于忙碌之中。套用这个观点,其实可以把GPU命令的准备阶段(运行在CPU上, 包括app和gfx driver)也看成整个3D Pipeline的一部分。所谓图形驱动的异步性,就是使CPU和GPU同时忙碌起来,GPU在处理 上一批命令,而CPU正在准备下一批命令,从而提高系统并行度。

Alt text

Fence

Alt text

异步性虽然增加了系统整体的并行度,但也使图形驱动的实现变得更见困难和复杂,因为我们需要一种机制,能够保证资源访问的互斥, 并能查询某些资源在GPU端的使用状态。比如一张texture如果刚刚用于draw,紧接着对其调用glTexImage2D,驱动必须保证, 对这张texture的修改是在上一次的draw完成以后。在硬件层面,GPU可以在命令流中插入一些所谓的Fence命令,保证只有在Fence的条件 满足之后,才能执行接下来的命令。而在该段命令流完成之后,GPU则会更新一些状态(往往是内存中的一些计数单元),这些命令可以称为Update命令。 有了这种硬件支持,client端的驱动就有能力协调好各种buffer的访问同步了。一段命令流大致的执行阶段如下:

一般来说,驱动会负责维护好资源之间的同步,用户不必担心。但OpenGL和EGL也提供了一种叫sync objects的对象,可以用上述的Fence/Update 将GPU上命令的执行状态暴露给用户,以用来做更灵活的处理(比如用户想显示等待渲染完成)。一个sync刚创建时处于unsignaled状态。 当插入到命令流中被GPU执行过以后,就变成了signaled状态。

Android Native Fence

以上是描述一个render context的情形。当某些Buffer需要在多个context之间传输时,这种异步性又如何体现呢?在现如今的Android 或者Linux桌面平台上,都有所谓的系统合成者(SurfaceFlinger/DRI2)的存在。即APP渲染一块buffer之后,把这块buffer传给 合成者,由合成者负责合成所有的图层,并把结果显示到屏幕上。一种简单的实现方法是,APP等待自己的渲染命令完成,确保buffer已经 被填入正确的内容,然后再传给合成者。但这显然不是一种高效率的实现。假设APP下命令的时间为t1,而GPU渲染的时间为t2,则合成者 至少要等到t1 + t2之后,才能开始下相关的合成命令,假设所需的时间是t3,则GPU至少要等到t1 + t2 + t3之后,才能真正处理合成 命令。而我们希望合成者在t1 ~ t2之间就能发出合成命令,GPU在APP端的渲染完成之后(t1 + t2),就能立刻处理合成命令。同理, APP的渲染一般要等到合成者用完buffer,重新传回给APP端之后才能进行。但最完美的结果是,APP端可以提前可以下命令,在GPU处理完 合成命令后,就立刻处理下一帧的渲染命令。在这种生产者和消费者的环形工作模型下,CPU一直领先GPU几拍,两者始终忙碌于各自的工作。 对于系统渲染负载较高的情况,这种模型尤为有利!

安卓为了实现这套模型,引入了Android native fence对象。考虑到APP和Composer处于不同进程,所以这个对象的首要能力就是能 跨进程传输,所以native fence是用一个fd来表示的,可以用Binder进行传输,就跟native buffer一样。安卓又引入了一个叫 Fence的类,这个类是用来作引用计数的,唯一的成员变量就是一个fd。这个fd在kernel层对应一个叫struct fence的结构体。 另外还有两个重要的结构体叫做sync_timeline和sync_pt。 三者的介绍如下。

sync_timeline: A sync_timeline is a monotonically increasing timeline that should be implemented for each driver instance, such as a GL context, display controller, or 2D blitter. This is essentially a counter of jobs submitted to the kernel for a particular piece of hardware. It provides guarantees about the order of operations and allows hardware-specific implementations.

sync_pt: A sync_pt is a single value or point on a sync_timeline. A point has three states: active, signaled, and error. Points start in the active state and transition to the signaled or error states. For instance, when a buffer is no longer needed by an image consumer, this sync_point is signaled so that image producers know it is okay to write into the buffer again.

sync_fence: A sync_fence is a collection of sync_pts that often have different sync_timeline parents (such as for the display controller and GPU). These are the main primitives over which drivers and userspace communicate their dependencies. A fence is a promise from the kernel that it gives upon accepting work that has been queued and assures completion in a finite amount of time.

TBD

]]>
Android Binder环 2014-09-23T00:00:00+00:00 Rasterer http://rasterer.github.io/2014/09/Android-Binder环 Principle

上一文中提到了截屏时候BufferQueue的特殊性,即SurfaceFlinger作为IGraphicBufferProduer的Bp端,Bn端则在WMS或Screenshot进程中。SF通过这个Bp代理,就可以实现dequeueBuffer和queueBuffer。但内部的实现却没这么简单。这里要涉及到Android Binder通信的一个特性,作者在此将它命名为Binder环。

要理解Binder环,最好先从Binder的线程模型说起。一般处理事务的线程,会被实现成一个死循环。当有任务请求到来,就被唤醒起来工作。如果所有请求都处理完毕,则会重新进入睡眠状态,等待下一个任务的到来。这就是Binder线程最基本的模型:

void IPCThreadState::joinThreadPool(bool isMain)
{
...
    do {
        processPendingDerefs();
        // now get the next command to be processed, waiting if necessary
        result = getAndExecuteCommand();

        if (result < NO_ERROR && result != TIMED_OUT && result != -ECONNREFUSED && result != -EBADF) {
            ALOGE("getAndExecuteCommand(fd=%d) returned unexpected error %d, aborting",
                  mProcess->mDriverFD, result);
            abort();
        }
        
        // Let this thread exit the thread pool if it is no longer
        // needed and it is not the main process thread.
        if(result == TIMED_OUT && !isMain) {
            break;
        }
    } while (result != -ECONNREFUSED && result != -EBADF);
...
}

有了Binder线程以后,当前进程就可以作为一个server开始提供服务。但还有一个问题。如果这个Server需要同时为很多的Clients提供服务,如system_server,那必须保证该Server有一定的吞吐能力。为此引入了Binder线程池的概念。从上面提及的函数名joinThreadPool也可以看出,该函数将自身加入到线程池中,作为一个专门处理Binder请求的线程。线程池中可以有多个Binder线程,可以同时处理多路请求。并且考虑到动态负载,当线程池中的线程耗尽时(都处于工作中),就会spawn一个新的线程(BR_SPAWN_LOOPER)。当然用户可以给一个线程池的最大线程数设定限制。理论上说,当负载降低的时候,应该释放线程池中的线程资源,这是最完美的。但目前Android并未实现此功能。当然这也不是必要的,因为当系统资源不足的时候,lowmemorykiller就经常会拿这些空闲的Binder线程开刀。

有了线程池的概念之后,就可以理解Binder环了。说白了,这是Binder驱动在接收请求时做的一个优化。之所以称之为Binder环,是因为Binder通信不是简单的1:1,当进程A请求B完成某件事的时候,B可能又依赖于C。而在特殊场合下,C又可能依赖于A,所以就构成了一个环状结构。现在抛出了一个问题:当请求回到A的时候,由线程池中哪个Binder线程来负责处理请求?
最佳答案是A中最开始发起的Binder线程。

Alt text Alt text

因为当请求回到进程A时,可以确定A1线程正在等待之前发送请求的回复,即处于阻塞状态,
所以我们可以顺便让A1起来干些别的事情。这种设计主要有两个优点:

  1. 更高效地利用线程池中的线程,避免不必要的线程增加,节省系统资源。

  2. 简化程序结构,特别是数据同步/锁保护,随机的Binder线程响应模式可能会增加锁的设计难度。

Binder驱动实现代码:

void binder_transaction()
{
	...
	while (tmp) {
		if (tmp->from && tmp->from->proc == target_proc)
			target_thread = tmp->from;
		tmp = tmp->from_parent;
	}

	...
}

Usage Scenario

在上一文中讨论的SurfaceFlinger截屏的实现中实际上就很巧妙地用到了Binder环。
直接引入实现的关键类:

class GraphicProducerWrapper : public BBinder, public MessageHandler {

大致的过程是:

  1. 一个Binder线程收到截屏请求之后,post一个消息让SF主线程处理

  2. SF按照上一文的描述,做如下工作:dequeue一块buffer -> 合成 -> queueBuffer

但是特殊的是在第 1 步中, Binder线程同时创建了一个GraphicProducerWrapper类的实例,并把该对象放到消息里面一起post给了SF。SF在处理dequeueBuffer和queueBuffer的时候,正常情况下会调用一个IGraphicBufferProducer接口的Binder对象。但是我们注意到,GraphicProducerWrapper类正好也继承了BBinder,所以实际上SF主线程操作的是一个BBinder的transact函数,即GraphicProducerWrapper实现的transact函数:

/*
     * this is called by our "fake" BpGraphicBufferProducer. We package the
     * data and reply Parcel and forward them to the calling thread.
     */
    virtual status_t transact(uint32_t code,
            const Parcel& data, Parcel* reply, uint32_t flags) {
        this->code = code;
        this->data = &data;
        this->reply = reply;
        android_atomic_acquire_store(0, &memoryBarrier);
        if (exitPending) {
            // if we've exited, we run the message synchronously right here
            handleMessage(Message(MSG_API_CALL));
        } else {
            barrier.close();
            looper->sendMessage(this, Message(MSG_API_CALL));
            barrier.wait();
        }
        return result;
    }

这里做的其实就是发了一个消息给之前的Binder线程,将dequeueBuffer或queueBuffer的请求又转回到该线程中。 而Binder线程的消息处理函数也呼之欲出:

/*
	 * here we run on the binder calling thread. All we've got to do is
	 * call the real BpGraphicBufferProducer.
	 */
    virtual void handleMessage(const Message& message) {
        android_atomic_release_load(&memoryBarrier);
        if (message.what == MSG_API_CALL) {
            //the transact result needs to be held to let main thread know
            result = impl->asBinder()->transact(code, data[0], reply);
            barrier.open();
        } else if (message.what == MSG_EXIT) {
            exitRequested = true;
        }
    }

这里真正地去做跨进程通信了。

总结一下,就是通过GraphicProducerWrapper在SF主线程的处理流中做了一个HOOK,看似在主线程中做了所有事情,实际上,真正的Binder通信请求又被委派到了最初的Binder线程中。引入这个Wrapper有什么好处呢?没错,就是利用了Binder环。如果用原先接收截屏请求的Binder线程来与请求方通信,那么就会构成一个Binder环,请求方只要再复用之前发CAPTURE_SCREEN请求的线程就可以了。

顺带一提的是,上一段代码在旧版本中有一处bug,即binder通信的结果result并没有保存。如果binder传输失败(对方进程死亡),因为没有正确返回错误码,会导致出现TOMBSTONE。在最新的代码中已被修复。

]]>
Android Capture Screen 2014-09-22T00:00:00+00:00 Rasterer http://rasterer.github.io/2014/09/Android-Capture-Screen 当同时按下POWER键和音量下键的时候,安卓就会产生截屏操作。触发的场景不限于此,按HOME或BACK键退出app,或是发生横竖屏切换的时候,都会触发截屏,只不过此时没有截屏动画。也可以使用以下命令进行截屏。

adb shell screencap -p /sdcard/tmp.png

只有系统的图层合成者(composer)才知道当前系统中各个图层(layer)是什么样子,所以截屏工作的主要承担者就是安卓的Composer——SurfaceFlinger(SF)。SF中维护了一个系统的图层集合(layer list),考虑到某些图层可能是半透明的,所以会将图层按Z序从下到上排好顺序,方便后续合成。SF会把当前所有可见的图层,都通过OpenGL ES合成进一个buffer中。因为是离屏渲染,所以这一步一般是通过framebuffer object(FBO)来实现的。具体的实现很简单,对每个图层来说都画一个矩形,将图层作为一张纹理,即可贴到FBO的目标buffer中。合成完以后,可以用合成的buffer来创建一个新的layer,用来显示截屏动画或是旋转动画,或是简单地保存成一个图片文件。

因为OpenGL是系统无关的,所以Android提供的native buffer不能直接作为render target被GLES使用。幸好,EGL已经提供了办法。利用EGL的API可以将一个本地窗口系统提供的native buffer转换成一个EGLImage,这是EGL用来实现在多种context之间共享资源的途径。利用它,native buffer就可以光明正大的attach到一个FBO上,作为截屏时候的渲染目标。

截屏还有个特殊性。一般情况下,Android BufferQueue的Bp端在APP(生产者),Bn端在SF(消费者)。在截屏场景下却是相反的,因为SF成了生产者。截屏时,screenshot进程或WMS,会发送一个IGraphicBufferProducer的匿名Binder给SF,SF进程正是使用这个Binder,来dequeue一块buffer -> 合成 -> queueBuffer的。具体的细节可参考下一篇——Android Binder环

大致的代码过程如下:

result = native_window_dequeue_buffer_and_wait(window,  &buffer);
...

EGLImageKHR image = eglCreateImageKHR(mEGLDisplay, EGL_NO_CONTEXT,
    EGL_NATIVE_BUFFER_ANDROID, buffer, NULL);
...

    glGenTextures(1, &tname);
    glBindTexture(GL_TEXTURE_2D, tname);
    glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)image);

    // create a Framebuffer Object to render into
    glGenFramebuffers(1, &name);
    glBindFramebuffer(GL_FRAMEBUFFER, name);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tname, 0);
...

        for (size_t i=0 ; i<count ; ++i) {
            const sp<Layer>& layer(layers[i]);
            const Layer::State& state(layer->getDrawingState());
            if (state.layerStack == hw->getLayerStack()) {
                if (state.z >= minLayerZ && state.z <= maxLayerZ) {
                    if (layer->isVisible()) {
                        if (filtering) layer->setFiltering(true);
                        layer->draw(hw);
                        if (filtering) layer->setFiltering(false);
                    }
                }
            }
        }
...

EGLSyncKHR sync = eglCreateSyncKHR(mEGLDisplay, EGL_SYNC_FENCE_KHR, NULL);
if (sync != EGL_NO_SYNC_KHR) {
    EGLint result = eglClientWaitSyncKHR(mEGLDisplay, sync, //等待合成完成
        EGL_SYNC_FLUSH_COMMANDS_BIT_KHR, 2000000000 /*2 sec*/);
...

window->queueBuffer(window, buffer, -1);
]]>
Hello Zhebin 2014-09-20T00:00:00+00:00 Rasterer http://rasterer.github.io/2014/09/Hello-Zhebin