client和sink之间的数据传递与同步

本文详细解析了PulseAudio框架中ALSA Sink模块的数据传输过程,包括mmap模式下的数据读取及fifo数据传输触发机制,并对比了不同sink模块之间的差异。

之前分析过代码,也能基本确定大概的数据传输与数据同步的过程。但是没有实践总是感觉虚的很,所以通过GDB验证了自己前段时间的分析。

 通过在命令行模式下执行

 >>> play-file /alpcode/s11.wav 0

将数据播放到sink 0,就是alsa-sink。

以下是代码在唤醒rtpoll之前的一个backtrace。下面简单分析一下这个过程。

#5  0xb7ef39f1 in sink_input_pop_cb (i=0x85228a0, length=5792, chunk=0xb78c5d60) at pulsecore/sound-file-stream.c:199
#6  0xb7ee0261 in pa_sink_input_peek (i=0x85228a0, slength=23168, chunk=0xb78c5dfc, volume=0xb78c5e08) at pulsecore/sink-input.c:654
#7  0xb7ee9602 in fill_mix_info (s=0x856beb0, length=0xb78c8194, info=0xb78c5dfc, maxinfo=32) at pulsecore/sink.c:752
#8  0xb7eeaa3d in pa_sink_render_into (s=0x856beb0, target=0xb78c81e0) at pulsecore/sink.c:991
#9  0xb7eeb08f in pa_sink_render_into_full (s=0x856beb0, target=0xb78c8284) at pulsecore/sink.c:1075
#10 0xb305c5cc in mmap_write (u=0x8524308, sleep_usec=0xb78c8358, polled=false, on_timeout=false) at modules/alsa/alsa-sink.c:605
#11 0xb305fd9f in thread_func (userdata=0x8524308) at modules/alsa/alsa-sink.c:1368
#12 0xb7e39444 in internal_thread_func (userdata=0x856d2d8) at pulsecore/thread-posix.c:72
#13 0xb7b8350f in start_thread () from /lib/tls/i686/cmov/libpthread.so.0
#14 0xb7b00a0e in clone () from /lib/tls/i686/cmov/libc.so.6

1. 由于alsa-sink使用mmap,所以从#10, mmap_write开始, 如果disable mmap,那么从unix_write开始。这个是在rtpoll被唤醒后开始执行的,何时唤醒?.

2.  #9#8是用于render数据的函数调用,如果不是mmap模式,直接调用pa_sink_render.

3. #7是个比较重要的调用,无论pa_sink_render还是#9都是通过这个调用获取数据,该函数的返回表示有几个流在播放,有多个是要做混音的

4. #6通过调用client注册的callback来取数据,在这个例子中,调用的是sound-file-stream.c中定义的函数  sink_input_pop_cb

5. 分析 sink_input_pop_cb 这个函数,他调用memblockq的一对函数,peek和push。如果能够peek到数据,返回,如果不能,则从文件读书到memblock,然后push,再返回到循环peek数据。看看函数实现会更清楚。

总结一下,在播放的过程中,sink的工作线程如果接受到触发(rtpoll), 开始接收数据 (通过pa_sink_render这样的函数,有mmap的稍微有些区别)。这个接受过程最终调用client的一个callback,其执行过程是

a> 要求读,如果读到数据返回

b> 如果读不到,生成数据(比如从wav文件读取到memory),送入memblockq, 返回a

需要注意的是,rtpoll是如何被唤醒的,什么时候唤醒。这里我之前的理解是不对的,我总以为是client和sink在通信,是client通过pipe唤醒sink线程。但是发现不是这样。至少我现在看pipe-sink不是这样。

module-pipe-sink数据拉动

先看这个简单的吧。调试这个sink的时候发现,播放数据到这个sink没有办法触发这个sink的读处理函数,gdb发现 pollfd的revents总是为0的,表示没有需要的时间发生,

            if
 (pollfd->revents)
{
                if
 (process_render(u) < 0)
                    goto
 fail;

                pollfd->revents = 0;
            }

 

在线程里面有这么一段代码,所以process_render不会被执行。

开始的时候没有在意看events,后来看了一下events的值为4,查一下定义

 #define POLLOUT         0x004           /* Writing now will not block.  */

表示能写就写,就可以触发。

为什么我播放了却没有触发,这就是我之前的错误理解导致思维定式,以为client写一下pipe才能有事件,但是看看这个fd,发现其为一个fifo, 这个sink就是要将数据播放到一个fifo里面。而events定义的POLLOUT表示能写就写,只要不阻塞。

但是由于我长时间测试,一个wav文件也不小,播放几次就吧fifo充满了,所以block了,所以没有置位,所以认为没有事件发生。

如果在命令行下

cat /home/cuigang/.pulse/8937d1b145b1b108a6a21d2c49782cf1-runtime/fifo_output

这样,sink一直都能收到事件。

ALSA SINK 数据拉动

在alsa的sink代码中,拉动数据的也是POLLOUT. 但是在设置poll关注事件的过程上,没有上面的pipe-sink那么直接, 这里是调用alsa的lib来实现的。

pa_rtpoll_item* pa_alsa_build_pollfd(snd_pcm_t *pcm, pa_rtpoll *rtpoll) {
    int
 n, err;
    struct pollfd *pollfd;
    pa_rtpoll_item *item;

    pa_assert(pcm);

    if
 ((n = snd_pcm_poll_descriptors_count(pcm)) < 0) {
        pa_log("snd_pcm_poll_descriptors_count() failed: %s"
, pa_alsa_strerror(n));
        return
 NULL;
    }

    item = pa_rtpoll_item_new(rtpoll, PA_RTPOLL_NEVER, (unsigned) n);
    pollfd = pa_rtpoll_item_get_pollfd(item, NULL);

    if
 ((err = snd_pcm_poll_descriptors(pcm, pollfd, (unsigned) n)) < 0) {
        pa_log("snd_pcm_poll_descriptors() failed: %s"
, pa_alsa_strerror(err));
        pa_rtpoll_item_free(item);
        return
 NULL;
    }

    return
 item;
}

用gdb看看 snd_pcm_poll_descriptors 调用后,这个fd都关心那些事件

 (gdb) p *pollfd

$35 = {fd = 28, events = 41, revents = 0}

然后在执行的时候,这个fd上发生了事件是1,经过snd_pcm_poll_descriptors_revents这个函数后,revents返回4. 要看看这个函数搞什么

 Breakpoint 5, thread_func (userdata=0x8acd3e0) at modules/alsa/alsa-sink.c:1444
1444                if
 ((err = snd_pcm_poll_descriptors_revents(u->pcm_handle, pollfd, n, &revents)) < 0) {
(gdb) p pollfd.revents
$37 = 1
(gdb) n
1449                if
 (revents & ~POLLOUT) {
(gdb) p revents
$38 = 4
(gdb) p pollfd.revents
$39 = 1
(gdb) p pollfd.events
$40 = 41
(gdb)

 
alsa sink的工作线程中有这么一段代码,用于获取已经发生的poll事件

            if
 ((err = snd_pcm_poll_descriptors_revents(u->pcm_handle, pollfd, n, &revents)) < 0){
               pa_log("snd_pcm_poll_descriptors_revents() failed: %s"
, pa_alsa_strerror(err));
               goto
 fail;
            }

返回值,revents. 这个值部分 决定了是否要向alsa写入数据,如下:

            if
 (u->use_mmap)
                work_done = mmap_write(u, &sleep_usec, revents & POLLOUT, on_timeout);
            else

                work_done = unix_write(u, &sleep_usec, revents & POLLOUT, on_timeout);

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值