App-MHFS

 view release on metacpan or  search on metacpan

share/public_html/static/music_worklet_inprogress/decoder/deps/miniaudio/miniaudio.h  view on Meta::CPAN

            return result;
        }

        MA_ZERO_OBJECT(&caps);
        caps.dwSize = sizeof(caps);
        hr = ma_IDirectSound_GetCaps(pDirectSound, &caps);
        if (FAILED(hr)) {
            ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "[DirectSound] IDirectSound_GetCaps() failed for playback device.");
            return ma_result_from_HRESULT(hr);
        }


        /* Channels. Only a single channel count is reported for DirectSound. */
        if ((caps.dwFlags & MA_DSCAPS_PRIMARYSTEREO) != 0) {
            /* It supports at least stereo, but could support more. */
            DWORD speakerConfig;

            channels = 2;

            /* Look at the speaker configuration to get a better idea on the channel count. */
            hr = ma_IDirectSound_GetSpeakerConfig(pDirectSound, &speakerConfig);
            if (SUCCEEDED(hr)) {
                ma_get_channels_from_speaker_config__dsound(speakerConfig, &channels, NULL);
            }
        } else {
            /* It does not support stereo, which means we are stuck with mono. */
            channels = 1;
        }


        /*
        In DirectSound, our native formats are centered around sample rates. All formats are supported, and we're only reporting a single channel
        count. However, DirectSound can report a range of supported sample rates. We're only going to include standard rates known by miniaudio
        in order to keep the size of this within reason.
        */
        if ((caps.dwFlags & MA_DSCAPS_CONTINUOUSRATE) != 0) {
            /* Multiple sample rates are supported. We'll report in order of our preferred sample rates. */
            size_t iStandardSampleRate;
            for (iStandardSampleRate = 0; iStandardSampleRate < ma_countof(g_maStandardSampleRatePriorities); iStandardSampleRate += 1) {
                ma_uint32 sampleRate = g_maStandardSampleRatePriorities[iStandardSampleRate];
                if (sampleRate >= caps.dwMinSecondarySampleRate && sampleRate <= caps.dwMaxSecondarySampleRate) {
                    pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].format     = ma_format_unknown;
                    pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].channels   = channels;
                    pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].sampleRate = sampleRate;
                    pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].flags      = 0;
                    pDeviceInfo->nativeDataFormatCount += 1;
                }
            }
        } else {
            /* Only a single sample rate is supported. */
            pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].format     = ma_format_unknown;
            pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].channels   = channels;
            pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].sampleRate = caps.dwMaxSecondarySampleRate;
            pDeviceInfo->nativeDataFormats[pDeviceInfo->nativeDataFormatCount].flags      = 0;
            pDeviceInfo->nativeDataFormatCount += 1;
        }

        ma_IDirectSound_Release(pDirectSound);
    } else {
        /*
        Capture. This is a little different to playback due to the say the supported formats are reported. Technically capture
        devices can support a number of different formats, but for simplicity and consistency with ma_device_init() I'm just
        reporting the best format.
        */
        ma_IDirectSoundCapture* pDirectSoundCapture;
        WORD channels;
        WORD bitsPerSample;
        DWORD sampleRate;

        result = ma_context_create_IDirectSoundCapture__dsound(pContext, ma_share_mode_shared, pDeviceID, &pDirectSoundCapture);
        if (result != MA_SUCCESS) {
            return result;
        }

        result = ma_context_get_format_info_for_IDirectSoundCapture__dsound(pContext, pDirectSoundCapture, &channels, &bitsPerSample, &sampleRate);
        if (result != MA_SUCCESS) {
            ma_IDirectSoundCapture_Release(pDirectSoundCapture);
            return result;
        }

        ma_IDirectSoundCapture_Release(pDirectSoundCapture);

        /* The format is always an integer format and is based on the bits per sample. */
        if (bitsPerSample == 8) {
            pDeviceInfo->nativeDataFormats[0].format = ma_format_u8;
        } else if (bitsPerSample == 16) {
            pDeviceInfo->nativeDataFormats[0].format = ma_format_s16;
        } else if (bitsPerSample == 24) {
            pDeviceInfo->nativeDataFormats[0].format = ma_format_s24;
        } else if (bitsPerSample == 32) {
            pDeviceInfo->nativeDataFormats[0].format = ma_format_s32;
        } else {
            return MA_FORMAT_NOT_SUPPORTED;
        }

        pDeviceInfo->nativeDataFormats[0].channels   = channels;
        pDeviceInfo->nativeDataFormats[0].sampleRate = sampleRate;
        pDeviceInfo->nativeDataFormats[0].flags      = 0;
        pDeviceInfo->nativeDataFormatCount = 1;
    }

    return MA_SUCCESS;
}



static ma_result ma_device_uninit__dsound(ma_device* pDevice)
{
    MA_ASSERT(pDevice != NULL);

    if (pDevice->dsound.pCaptureBuffer != NULL) {
        ma_IDirectSoundCaptureBuffer_Release((ma_IDirectSoundCaptureBuffer*)pDevice->dsound.pCaptureBuffer);
    }
    if (pDevice->dsound.pCapture != NULL) {
        ma_IDirectSoundCapture_Release((ma_IDirectSoundCapture*)pDevice->dsound.pCapture);
    }

    if (pDevice->dsound.pPlaybackBuffer != NULL) {
        ma_IDirectSoundBuffer_Release((ma_IDirectSoundBuffer*)pDevice->dsound.pPlaybackBuffer);
    }
    if (pDevice->dsound.pPlaybackPrimaryBuffer != NULL) {

share/public_html/static/music_worklet_inprogress/decoder/deps/miniaudio/miniaudio.h  view on Meta::CPAN

    pCallbacks->onDeviceWrite             = ma_device_write__alsa;
    pCallbacks->onDeviceDataLoop          = NULL;
    pCallbacks->onDeviceDataLoopWakeup    = ma_device_data_loop_wakeup__alsa;

    return MA_SUCCESS;
}
#endif  /* ALSA */



/******************************************************************************

PulseAudio Backend

******************************************************************************/
#ifdef MA_HAS_PULSEAUDIO
/*
The PulseAudio API, along with Apple's Core Audio, is the worst of the maintream audio APIs. This is a brief description of what's going on
in the PulseAudio backend. I apologize if this gets a bit ranty for your liking - you might want to skip this discussion.

PulseAudio has something they call the "Simple API", which unfortunately isn't suitable for miniaudio. I've not seen anywhere where it
allows you to enumerate over devices, nor does it seem to support the ability to stop and start streams. Looking at the documentation, it
appears as though the stream is constantly running and you prevent sound from being emitted or captured by simply not calling the read or
write functions. This is not a professional solution as it would be much better to *actually* stop the underlying stream. Perhaps the
simple API has some smarts to do this automatically, but I'm not sure. Another limitation with the simple API is that it seems inefficient
when you want to have multiple streams to a single context. For these reasons, miniaudio is not using the simple API.

Since we're not using the simple API, we're left with the asynchronous API as our only other option. And boy, is this where it starts to
get fun, and I don't mean that in a good way...

The problems start with the very name of the API - "asynchronous". Yes, this is an asynchronous oriented API which means your commands
don't immediately take effect. You instead need to issue your commands, and then wait for them to complete. The waiting mechanism is
enabled through the use of a "main loop". In the asychronous API you cannot get away from the main loop, and the main loop is where almost
all of PulseAudio's problems stem from.

When you first initialize PulseAudio you need an object referred to as "main loop". You can implement this yourself by defining your own
vtable, but it's much easier to just use one of the built-in main loop implementations. There's two generic implementations called
pa_mainloop and pa_threaded_mainloop, and another implementation specific to GLib called pa_glib_mainloop. We're using pa_threaded_mainloop
because it simplifies management of the worker thread. The idea of the main loop object is pretty self explanatory - you're supposed to use
it to implement a worker thread which runs in a loop. The main loop is where operations are actually executed.

To initialize the main loop, you just use `pa_threaded_mainloop_new()`. This is the first function you'll call. You can then get a pointer
to the vtable with `pa_threaded_mainloop_get_api()` (the main loop vtable is called `pa_mainloop_api`). Again, you can bypass the threaded
main loop object entirely and just implement `pa_mainloop_api` directly, but there's no need for it unless you're doing something extremely
specialized such as if you want to integrate it into your application's existing main loop infrastructure.

(EDIT 2021-01-26: miniaudio is no longer using `pa_threaded_mainloop` due to this issue: https://github.com/mackron/miniaudio/issues/262.
It is now using `pa_mainloop` which turns out to be a simpler solution anyway. The rest of this rant still applies, however.)

Once you have your main loop vtable (the `pa_mainloop_api` object) you can create the PulseAudio context. This is very similar to
miniaudio's context and they map to each other quite well. You have one context to many streams, which is basically the same as miniaudio's
one `ma_context` to many `ma_device`s. Here's where it starts to get annoying, however. When you first create the PulseAudio context, which
is done with `pa_context_new()`, it's not actually connected to anything. When you connect, you call `pa_context_connect()`. However, if
you remember, PulseAudio is an asynchronous API. That means you cannot just assume the context is connected after `pa_context_context()`
has returned. You instead need to wait for it to connect. To do this, you need to either wait for a callback to get fired, which you can
set with `pa_context_set_state_callback()`, or you can continuously poll the context's state. Either way, you need to run this in a loop.
All objects from here out are created from the context, and, I believe, you can't be creating these objects until the context is connected.
This waiting loop is therefore unavoidable. In order for the waiting to ever complete, however, the main loop needs to be running. Before
attempting to connect the context, the main loop needs to be started with `pa_threaded_mainloop_start()`.

The reason for this asynchronous design is to support cases where you're connecting to a remote server, say through a local network or an
internet connection. However, the *VAST* majority of cases don't involve this at all - they just connect to a local "server" running on the
host machine. The fact that this would be the default rather than making `pa_context_connect()` synchronous tends to boggle the mind.

Once the context has been created and connected you can start creating a stream. A PulseAudio stream is analogous to miniaudio's device.
The initialization of a stream is fairly standard - you configure some attributes (analogous to miniaudio's device config) and then call
`pa_stream_new()` to actually create it. Here is where we start to get into "operations". When configuring the stream, you can get
information about the source (such as sample format, sample rate, etc.), however it's not synchronous. Instead, a `pa_operation` object
is returned from `pa_context_get_source_info_by_name()` (capture) or `pa_context_get_sink_info_by_name()` (playback). Then, you need to
run a loop (again!) to wait for the operation to complete which you can determine via a callback or polling, just like we did with the
context. Then, as an added bonus, you need to decrement the reference counter of the `pa_operation` object to ensure memory is cleaned up.
All of that just to retrieve basic information about a device!

Once the basic information about the device has been retrieved, miniaudio can now create the stream with `ma_stream_new()`. Like the
context, this needs to be connected. But we need to be careful here, because we're now about to introduce one of the most horrific design
choices in PulseAudio.

PulseAudio allows you to specify a callback that is fired when data can be written to or read from a stream. The language is important here
because PulseAudio takes it literally, specifically the "can be". You would think these callbacks would be appropriate as the place for
writing and reading data to and from the stream, and that would be right, except when it's not. When you initialize the stream, you can
set a flag that tells PulseAudio to not start the stream automatically. This is required because miniaudio does not auto-start devices
straight after initialization - you need to call `ma_device_start()` manually. The problem is that even when this flag is specified,
PulseAudio will immediately fire it's write or read callback. This is *technically* correct (based on the wording in the documentation)
because indeed, data *can* be written at this point. The problem is that it's not *practical*. It makes sense that the write/read callback
would be where a program will want to write or read data to or from the stream, but when it's called before the application has even
requested that the stream be started, it's just not practical because the program probably isn't ready for any kind of data delivery at
that point (it may still need to load files or whatnot). Instead, this callback should only be fired when the application requests the
stream be started which is how it works with literally *every* other callback-based audio API. Since miniaudio forbids firing of the data
callback until the device has been started (as it should be with *all* callback based APIs), logic needs to be added to ensure miniaudio
doesn't just blindly fire the application-defined data callback from within the PulseAudio callback before the stream has actually been
started. The device state is used for this - if the state is anything other than `ma_device_state_starting` or `ma_device_state_started`, the main data
callback is not fired.

This, unfortunately, is not the end of the problems with the PulseAudio write callback. Any normal callback based audio API will
continuously fire the callback at regular intervals based on the size of the internal buffer. This will only ever be fired when the device
is running, and will be fired regardless of whether or not the user actually wrote anything to the device/stream. This not the case in
PulseAudio. In PulseAudio, the data callback will *only* be called if you wrote something to it previously. That means, if you don't call
`pa_stream_write()`, the callback will not get fired. On the surface you wouldn't think this would matter because you should be always
writing data, and if you don't have anything to write, just write silence. That's fine until you want to drain the stream. You see, if
you're continuously writing data to the stream, the stream will never get drained! That means in order to drain the stream, you need to
*not* write data to it! But remember, when you don't write data to the stream, the callback won't get fired again! Why is draining
important? Because that's how we've defined stopping to work in miniaudio. In miniaudio, stopping the device requires it to be drained
before returning from ma_device_stop(). So we've stopped the device, which requires us to drain, but draining requires us to *not* write
data to the stream (or else it won't ever complete draining), but not writing to the stream means the callback won't get fired again!

This becomes a problem when stopping and then restarting the device. When the device is stopped, it's drained, which requires us to *not*
write anything to the stream. But then, since we didn't write anything to it, the write callback will *never* get called again if we just
resume the stream naively. This means that starting the stream requires us to write data to the stream from outside the callback. This
disconnect is something PulseAudio has got seriously wrong - there should only ever be a single source of data delivery, that being the
callback. (I have tried using `pa_stream_flush()` to trigger the write callback to fire, but this just doesn't work for some reason.)

Once you've created the stream, you need to connect it which involves the whole waiting procedure. This is the same process as the context,
only this time you'll poll for the state with `pa_stream_get_status()`. The starting and stopping of a streaming is referred to as
"corking" in PulseAudio. The analogy is corking a barrel. To start the stream, you uncork it, to stop it you cork it. Personally I think
it's silly - why would you not just call it "starting" and "stopping" like any other normal audio API? Anyway, the act of corking is, you
guessed it, asynchronous. This means you'll need our waiting loop as usual. Again, why this asynchronous design is the default is
absolutely beyond me. Would it really be that hard to just make it run synchronously?

Teardown is pretty simple (what?!). It's just a matter of calling the relevant `_unref()` function on each object in reverse order that
they were initialized in.



( run in 0.453 second using v1.01-cache-2.11-cpan-96521ef73a4 )