Strange PulseAudio monitor device behaviour

2019-07-28 02:46发布

Faced strange PulseAudio monitor device (i.e. audio input device which plays sound sent to speaker) behaviour. I've reduced code from my real project to simple example based on code from PulseAudio docs https://freedesktop.org/software/pulseaudio/doxygen/parec-simple_8c-example.html, I've only added time limit and read bytes counting. It works for example 30 seconds and prints read bytes count. Problem is that bytes count vastly differs if something is played during program run. I've executed this program and in parallel executed bash for loop consisting aplay with short tada.wav file. Difference is 9%. To test it more, I tried to run 4 such loops in parallel with PulseAudio example and difference is even more - 34%. But if instead of several aplay with short wav I run mplayer with long mp3 file - there is no such difference, bytes count is similar to case when no sound is played.

Such behaviour causes failure of sound processing code in my real project, so if somebody could suggest how to solve it - I'll be very grateful.

Similar code on Windows, Qt based and using Stereo Mixer device as analog of PulseAudio monitor works without such problems.

Here is my code based on example from PulseAudio docs:

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <iostream>
#include <chrono>

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

#include <pulse/simple.h>
#include <pulse/error.h>

#define BUFSIZE 1024

using namespace std;
using namespace std::chrono;

int main(int argc, char *argv[])
{
    int duration = 30000;
    int64_t readBytesCounter = 0;
    milliseconds msStart = duration_cast< milliseconds >(system_clock::now().time_since_epoch());

    /* The sample type to use */
    static const pa_sample_spec ss = {
        .format = PA_SAMPLE_S16LE,
        .rate = 44100,
        .channels = 2
    };
    pa_simple *s = NULL;
    int ret = 1;
    int error;
    /* Create the recording stream */
    if (!(s = pa_simple_new(NULL, argv[0], PA_STREAM_RECORD, "alsa_output.pci-0000_00_1b.0.analog-stereo.monitor", "record", &ss, NULL, NULL, &error))) {
        fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error));
        goto finish;
    }
    for (;;) {
        uint8_t buf[BUFSIZE];
        /* Record some data ... */
        if (pa_simple_read(s, buf, sizeof(buf), &error) < 0) {
            fprintf(stderr, __FILE__": pa_simple_read() failed: %s\n", pa_strerror(error));
            goto finish;
        }
        readBytesCounter += BUFSIZE;

        milliseconds msCurrent = duration_cast< milliseconds >(system_clock::now().time_since_epoch());
        int elapsed = msCurrent.count() - msStart.count();
        if (elapsed > duration)
        {
            cerr << int(elapsed / 1000) << " seconds elapsed, terminating" << endl;
            cerr << readBytesCounter << " bytes read" << endl;
            goto finish;
        }
    }
    ret = 0;
finish:
    if (s)
        pa_simple_free(s);
    return ret;
}

It could be built with following command:

g++ -o main main.cpp -lpulse -lpulse-simple -std=c++11

Example wav file i've taken from http://d0.waper.ru/f/462151/23/HgDwimvX37CwxWqW38eywg%2C1485353628/7d74/9/462151.wav/tada.wav

And here are test results:

Test 1. No sound in speaker

$ time ./main
30 seconds elapsed, terminating
5323776 bytes read

real    0m30.028s
user    0m0.168s
sys     0m0.388s

Test 2. Bash for loop "for i in seq 1 22; do aplay tada.wav; done" with short wav files in background. Bytes count increase is 5798912 / 5323776 = 1.089 times.

$ time ./main 
30 seconds elapsed, terminating
5798912 bytes read

real    0m30.023s
user    0m0.120s
sys     0m0.184s

Test 3. 4 Bash for loops with short wav files in background. Bytes count increase is 7129088 / 5323776 = 1.339 times.

$ time ./main 
30 seconds elapsed, terminating
7129088 bytes read

real    0m30.019s
user    0m0.164s
sys     0m0.196s

Test 4. mplayer with long mp3 in background. 5288960 / 5323776 = 0.993, i.e. no significant bytes count difference.

$ time ./main 
30 seconds elapsed, terminating
5288960 bytes read

real    0m30.024s
user    0m0.096s
sys     0m0.204s

Tried to execute a set of each test, averaged bytes count - similar difference.

P.S.: Configuration of my system:

  • OS Ubuntu 16.04.1 amd64
  • pulseaudio 1:8.0-0ubuntu3.2
  • alsa-base 1.0.25+dfsg-0ubuntu5

1条回答
Explosion°爆炸
2楼-- · 2019-07-28 03:34

Here is reply from Tanu Kaskinen from PulseAudio mailing list https://lists.freedesktop.org/archives/pulseaudio-discuss/2017-January/027412.html . Not a full answer, but a little explanation and workaround:

You're probably hitting a known bug we have with rewind handling in monitor sources ("known" means in this case that the symptoms are known, but not the exact cause). When a stream starts to play to the monitored sink, the sink will rewrite its playback buffer contents (this is called "rewinding"), and this causes a glitch in the monitor source (some extra audio apparently gets pushed to the recording stream, judging from your experiments). I hope to fix this some day, but it doesn't look like I'll have time for it in the near future. If you have the time and motivation to investigate and fix the bug, that would be awesome.

As a workaround, you can probably reduce the magnitude of the error by reducing the playback buffer size. To do that, pass tsched_buffer_size=X to module-udev-detect in /etc/pulse/default.pa (replace X with the buffer size in bytes)."

查看更多
登录 后发表回答