Matthew van Eerde's web log

  • Matthew van Eerde's web log

    Sample - WASAPI loopback capture (record what you hear)


    In a previous post I showed how to play silence to a given audio device and hinted at a possible application.

    Attached to this post is a sample WASAPI loopback capture app - amd64, x86 and source included.  This allows you to record the sound that is coming out of your speakers:

    >loopback-capture -?
    loopback-capture -?
    loopback-capture --list-devices
    loopback-capture [--device "Device long name"] [--file "file name"] [--int-16]

        -? prints this message.
        --list-devices displays the long names of all active playback devices.
        --device captures from the specified device (default if omitted)
        --file saves the output to a file (loopback-capture.wav if omitted))
        --int-16 attempts to coerce data to 16-bit integer format

    There are a couple of oddities for WASAPI loopback capture.  One is that "event mode" doesn't work for loopback capture; you can call pAudioClient->Initialize(... AUDCLNT_STREAMFLAGS_LOOPBACK | AUDCLNT_STREAMFLAGS_EVENTCALLBACK, ... ), you can call pAudioClient->SetEventHandle(...), and everything will succeed... but the "data is ready" event will never fire.  So this app creates its own waitable timer.

    Another oddity is that WASAPI will only push data down to the render endpoint when there are active streams.  When nothing is playing, there is nothing to capture.

    For example, play a song, and then run loopback-capture.  While loopback-capture is running, stop the song, and then start it again.  You'll get this output when you start it back up:

    Press Enter to quit...
    IAudioCaptureClient::GetBuffer set flags to 0x00000001 on pass 5381 after 1088829 frames

    Thread HRESULT is 0x8000ffff

    The flag in question is AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY.  When the song stopped, no more data was available to capture.  Eventually the song started up again, and WASAPI dutifully reported that there was a glitch detected.  This app stops on glitches.

    There are a couple of other possible ways to handle this.  One way is to ignore glitches; then if you stop a song, wait a few seconds, and start it again, then the recorded signal will omit the wait and abut the two "audio is playing" portions.

    But my particular favorite way of handling this is to run silence.exe.  That way there are never any "nothing is playing" glitches, because there's always something playing.

    EDIT 11/23/2009: Updated loopback-capture.exe to ignore the glitch flag on the first packet, since Windows 7 sets it.  Also improved the interaction between the capture thread bailing out and the user pressing Enter to finish.

    EDIT 11/5/2014: Go read this new post which has updated source and binaries, as well as links to better samples.

  • Matthew van Eerde's web log

    Sample - playing silence via WASAPI event-driven (pull) mode


    Be vewy vewy quiet - we'we hunting wabbits.
        -- Elmer Fudd

    Attached is a mini-app I've written to play silence to any given playback device using WASAPI event-driven (pull) mode.  Source, x86 binary, and amd64 binary are attached.

    Usage statement:

    >silence -?
    silence -?
    silence --list-devices
    silence --device "Device long name"

        With no arguments, plays silence to the default audio device.
        -? prints this message.
        --list-devices displays the long names of all active playback devices.
        --device plays silence to the specified device.

    >silence --list-devices
    Active render endpoints found: 2
        Speakers (USB Audio Device)
        Headphones (High Definition Audio Device)

    >silence --device "Headphones (High Definition Audio Device)"
    Press Enter to quit...
    Received stop event after 488 passes

    While it's playing it shows up in the Volume Mixer:

    Why would I write such a thing?

    Well, there is the pedagogical exercise of writing a WASAPI event-driven render loop.

    But there is also a practical application of an active silence stream, having to do with loopback capture.  More on this in a future post...

    EDIT: 7/30/2009 - fixed bug where I was treating the GetCurrentPadding value as the amount of free space in the buffer when in fact it's the amount of used space.

    While I was at it, added an icon and exited immediately on errors rather than waiting for the caller to hit Enter.

    EDIT: 9/28/2015 - moved source to

  • Matthew van Eerde's web log

    Good Perl, Bad Perl


    One of my favorite languages is Perl.  Perl has an ambivalent reputation; some people take to it, some accuse it of being a syntax-complete language.  (There's some truth to this.)

    My view is that Perl gives you a very direct link into the mind of the programmer - much more so than other languages.  Perl is designed very much like a spoken language, perhaps because Larry Wall's background is linguistics.

    There was a little girl
    Who had a little curl
    Right in the middle of her forehead.
    And when she was good,
    She was very, very, good;
    But when she was bad
    She was horrid.
       -- Henry Wadsworth Longfellow

    (In an English accent, "forehead" and "horrid" actually rhyme.)

    Two examples of my own Perl to illustrate my point.  This is in my email signature:

    perl -e "print join er,reverse',','l hack',' P','Just anoth'"

    And this little seasonal gem:

    use strict;
    use warnings;

    sub receive($);

    my @ordinals = qw(
    first second third fourth fifth sixth
    seventh eighth ninth tenth eleventh twelfth

    my @gifts = reverse split /\n/, <<END_OF_LIST;
    Twelve drummers drumming;
    Eleven pipers piping;
    Ten lords a-leaping;
    Nine ladies dancing;
    Eight maids a-milking;
    Seven swans a-swimming;
    Six geese a-laying;
    Five golden ringeds;
    Four colly birds;
    Three French hens;
    Two turtle doves;
    A partridge in a pear tree.

    for (my $day = 1; $day <= 12; $day++) {

    sub receive($) {
    my $day = shift;

    print("On the ", $ordinals[$day], " day of Christmas, my true love sent to me:\n");

    for (my $i = $day; $i > 0; $i--) {
    my $gift = $gifts[$i - 1];

    if ($i == 1 && $day != 1) {
    $gift =~ s/^(\s*)A/$1And a/;

    print $gift, "\n";

    if ($day != 12) {
    print "\n";

    The latter kind of Perl I like to call "good Perl".  It's easy to read, I think.  There are a couple of idioms that take getting used to, just like with any new language, but well-written Perl is (I think) easier to read than any other language.

    But flexibility has its dark sides as well.  Black Perl is the canonical example, but there are others such as Perl golf.  This kind of thing (the first sample above is an example) is responsible for at least part of Perl's reputation for opacity; its compatibility with shell scripting, and most particularly its embedded regular expression support, is responsible for much of the rest.

    Exercise: duplicate the output of the second sample above using as short a Perl program as possible.

Page 1 of 1 (3 items)

December, 2008