3

I was watching a Computerphile video about code golf and chiptunes and was interested in running the example code provided, but it relies on aplay, a Linux utility for the ALSA sound card driver, and I'd like to run this on Windows 7. Is there an equivalent program or utility on Windows 7+ (preferably but not necessarily OS-provided) that will take a stream of bytes and convert it to an audio stream?

0

same :)

I found you can use ffmpeg to convert it

ffmpeg -f u8 -i music.raw music.wav

Then use whatever you like to play it

Now, I tried piping it directly (vlc allows input on stdin, but it won't read raw data, at least not without arguments I'm not sure how to give it lol)

music.exe | ffmpeg ... -i pipe:1 | vlc.exe -

but ffmpeg said the pipe didn't have enough space (I tried pipe:0 as well since I wasn't 100% sure which it be for stdin on windows...)

so I ended up redirecting music.exe to a music.raw file for a small amount of time (music.exe > music.raw) then I could use this to pipe straight from ffmpeg to vlc

ffmpeg.exe -f u8 -i music.raw -f wav pipe:1 | vlc.exe -
  • This is as good an answer as any, so I'll mark it. I think the real answer is that there's no good direct pipe solution on windows – Ken Bellows Jan 11 '16 at 21:38
  • I'd like to comment this on yours but I don't have 50 rep :) unix.stackexchange.com/questions/205107/… I found the above with a google search so while the command may be a bit longer than ./a | aplay, you could theoretically do so without having to save the file. You could then add the command to a makefile/batch file etc. – FreeER Jan 11 '16 at 22:03
  • The main problem I've found with using play.exe from SoX is that it has a hard time interfacing with Windows sound drivers. Maybe I just don't know enough about SoX, but I haven't figured out yet how to get play.exe to work without just throwing an error – Ken Bellows Jan 12 '16 at 14:45
  • hm, the download I got for sox didn't actually have play.exe but I found that you can just specify an output of "-t -waveaudio default" instead of a file. btw, I threw it all up on dropbox if you're interested: dropbox.com/sh/ibb924waxkqga04/AAAa3yxp_lf7_awM2eY30bPqa?dl=0 – FreeER Jan 13 '16 at 5:59
  • 1
    I just found out that it actually works with ffmpeg if you do not specify a pipe number. Just type a.exe | ffmpeg.exe -ar 8000 -f u8 -i pipe: -f wav pipe: | vlc.exe - and you should be good to go. The option -ar sets the sample rate to 8000 Hz. By default ffmpeg uses 44100 Hz. – maddin45 Jan 17 '16 at 0:32
3

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;
}
1

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.

  • where is the bitstream source parameter? – nicolay.anykienko Dec 22 '18 at 11:04
0

For posterity: it seems like there's isn't really a good solution, but another approximate-enough one that I found was to use SoX with the following options to generate a .wav file:

./a.exe | sox -t raw -b 16 -e signed -r 8000 - test.wav

This creates a very large file very quickly, so don't let it run for too long. About 10 seconds on my machine (Intel i7 3.4GHz processor) generated about 2hrs of audio (~125KB).

  • Without knowing sox, you could tell it to output on stdout then pipe this to head -c <bytes> to limit the output to a number of bytes. samples/second × bytes/channel/sample × channels × seconds = bytes. You'll lose a few samples due to the header, but it's a quick and dirty method. Otherwise, sox has a command that will let you trim an input: sox <options> - test.wav trim 0 60, meaning it will skip 0 seconds and take 60 seconds from the input. – sleblanc Feb 7 '18 at 13:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.