Is there a Windows equivalent to the Linux "aplay" that will accept a bitstream and output audio?

Solution 1:

I watched exactly the same video and was very disappointed that I could not find a program for windows that behaves like aplay in this example.

In the end, I wrote one myself using C++ and OpenAL. I will post the code below. You will have to link to the OpenAL library to build the executable. The library is part of the OpenAL Core SDK which you can download from their website.

If you just want the executable, you can download it here. Look for yalpa.exe.

Syntax

Let's say you use my executable yalpa.exe. Then you can process your raw audio data by piping it to yalpa:

a.exe | yalpa.exe

Alternatively you could first write the audio data to a file and pass that file to yalpa's stdin:

yalpa.exe < my_audio.raw

Note: yalpa works in cmd, but not in PowerShell. Pipes seem to be handled differently there (see this related SO question).

The code:

Disclaimer: I can not guarantee that this code is 100% bug-free, but I tested it on two different machines with Windows 7 and Windows 10 respectively. It was compiled and linked using the Visual Studio 2013 compiler. I was also able to compile it using g++ on Cygwin, but OpenAL failed at runtime because of problems with pulseaudio.

Feel free to edit and use my code.

#include <iostream>
#include <cstdio>
#include <cstdint>
#include <thread>
#include <chrono>

#if defined _WIN32
#include <al.h>
#include <alc.h>
#include <io.h>
#include <fcntl.h>
#else
#include <AL/al.h>
#include <AL/alc.h>
#endif

#if 0 || defined _DEBUG
#define AL_CHECK_ERROR(msg) (checkALError(msg))
#else
#define AL_CHECK_ERROR(msg)
#endif

const uint8_t numBuf = 3;
const ALsizei bufSize = 1000;
const ALenum format = AL_FORMAT_MONO8;
const ALsizei freq = 8000;
char readBuf[bufSize]; 

void checkALError(const char * msg)
{
    while (ALuint err = alGetError() != AL_NO_ERROR)
        std::cerr << "Caught AL Error at " << msg << ": " << err << "\n";
}

ALsizei fillBufferFromStdin(ALuint buf)
{
    // read
    const ALsizei bytesRead = (ALsizei) fread(readBuf, sizeof(uint8_t), bufSize, stdin);
    // copy to OpenAL buffer
    alBufferData(buf, format, (void *) readBuf, bytesRead, freq);
    AL_CHECK_ERROR("buffer data");
    return bytesRead;
}

void updateBuffers(ALuint src, ALuint bufs[numBuf])
{
    ALint srcState;
    do
    {
        // wait until a buffer is free
        ALint val = 0;
        do 
        {
            alGetSourcei(src, AL_BUFFERS_PROCESSED, &val);
            AL_CHECK_ERROR("get num processed");
            if (val > 0) break;
            // sleep for a quarter of the duration a buffer plays
            std::this_thread::sleep_for(std::chrono::milliseconds((bufSize / freq) * 1000 / 4));
        } while (true);
        while (val--)
        {
            // remove oldest buffer from queue and get its id
            ALuint buf;
            alSourceUnqueueBuffers(src, 1, &buf);
            AL_CHECK_ERROR("unqueue buffer");
            // fill buffer
            const ALsizei bytesRead = fillBufferFromStdin(buf);
            // add buffer to queue
            alSourceQueueBuffers(src, 1, &buf);
            AL_CHECK_ERROR("queue buffer");
            // if end of stdin was reached, return
            if (bytesRead < bufSize) return;
        }
        // check if source is still playing
        alGetSourcei(src, AL_SOURCE_STATE, &srcState);
    } while (AL_PLAYING == srcState);
}

int main(int argc, char * argv[])
{
    std::cout << "OpenAL test project\n";
    // set stdin to binary mode
#ifdef _WIN32
    _setmode(_fileno(stdin), _O_BINARY);
#else
    freopen(nullptr, "rb", stdin);
#endif

    // initialization: open default device
    ALCdevice * dev = alcOpenDevice(nullptr);
    // reset error state
    AL_CHECK_ERROR("open device");
    // create a context
    ALCcontext * context = alcCreateContext(dev, nullptr);
    AL_CHECK_ERROR("create context");
    alcMakeContextCurrent(context);
    AL_CHECK_ERROR("activate context");
    // create buffers for audio streaming
    ALuint bufs[numBuf];
    alGenBuffers(numBuf, bufs);
    AL_CHECK_ERROR("create buffer");
    // create source to play buffer
    ALuint src;
    alGenSources(1, &src);
    AL_CHECK_ERROR("create source");

    // initially fill buffers
    for (uint8_t i = 0; i < numBuf; ++i) fillBufferFromStdin(bufs[i]);
    alSourceQueueBuffers(src, numBuf, bufs);
    AL_CHECK_ERROR("queue buffer");
    // play source
    alSourcePlay(src);
    AL_CHECK_ERROR("play");
    // fill buffers in loop
    updateBuffers(src, bufs);
    // when stream is read completely, wait until source stops playing
    ALint srcState;
    do
    {
        alGetSourcei(src, AL_SOURCE_STATE, &srcState);
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    } while (AL_PLAYING == srcState);


    // delete source
    alDeleteSources(1, &src);
    AL_CHECK_ERROR("delete source");
    // delete buffers
    alDeleteBuffers(numBuf, bufs);
    AL_CHECK_ERROR("delete buffer");
    // destroy context
    alcDestroyContext(context);
    AL_CHECK_ERROR("destroy context");
    // close device
    alcCloseDevice(dev);
    AL_CHECK_ERROR("close device");

    std::cout << "Exiting\n";
    return 0;
}

Solution 2:

Using vlc and some command-line arguments I copy-pasted from the internet you are able to recreate aplay's functionality on windows.

audio.exe | vlc --demux=rawaud --rawaud-channels 2 --rawaud-samplerate 8000 -

Credit should go to this post and FreeER's answer.