switchbrew / switch-examples

Switch examples for devkitA64 and libnx.

Home Page:https://devkitpro.org/viewforum.php?f=42

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

hwopus example sound stutters

jakibaki opened this issue · comments

Running the hwopus example results in a stuttering sound even after removing the printf and consoleUpdate calls.

fw 6.1 libnx stable

That's normal, see comments (although stuttering isn't specifically mentioned).

Sorry but the only comment about this that I could find is the one telling people to run this in a seperate thread. I just did that as an experiment but it doesn't seem to change anything.

code?

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <inttypes.h>
#include <switch.h>
#include <opus/opusfile.h>

#include "sample_opus.h"

// Sample comes from this website:
// https://www.soundjay.com/magic-sound-effect.html

//Example for playing audio decoded with hwopus using audren. This decodes Opus in hardware. For encoding this is not available with hwopus, hence that has to be done in software.
//Requires package switch-opusfile.
//This uses libopusfile, see also the docs for that.
//Note that actual apps should handle audio on a dedicated thread.

static size_t opuspkt_tmpbuf_size = sizeof(HwopusHeader) + 4096*48;
static u8* opuspkt_tmpbuf;

int hw_decode(void *_ctx, OpusMSDecoder *_decoder, void *_pcm, const ogg_packet *_op, int _nsamples, int _nchannels, int _format, int _li) {
    HwopusDecoder *decoder = (HwopusDecoder*)_ctx;
    HwopusHeader *hdr = NULL;
    size_t pktsize, pktsize_extra;

    Result rc = 0;
    s32 DecodedDataSize = 0;
    s32 DecodedSampleCount = 0;

    if (_format != OP_DEC_FORMAT_SHORT) return OPUS_BAD_ARG;

    pktsize = _op->bytes;//Opus packet size.
    pktsize_extra = pktsize+8;//Packet size with HwopusHeader.

    if (pktsize_extra > opuspkt_tmpbuf_size) return OPUS_INTERNAL_ERROR;

    hdr = (HwopusHeader*)opuspkt_tmpbuf;
    memset(opuspkt_tmpbuf, 0, pktsize_extra);

    hdr->size = __builtin_bswap32(pktsize);
    memcpy(&opuspkt_tmpbuf[sizeof(HwopusHeader)], _op->packet, pktsize);

    rc = hwopusDecodeInterleaved(decoder, &DecodedDataSize, &DecodedSampleCount, opuspkt_tmpbuf, pktsize_extra, _pcm, _nsamples * _nchannels * sizeof(opus_int16));

    if (R_FAILED(rc)) return OPUS_INTERNAL_ERROR;
    if (DecodedDataSize != pktsize_extra || DecodedSampleCount != _nsamples) return OPUS_INVALID_PACKET;

    return 0;
}


int thread_test();

int main()
{
    socketInitializeDefault();
    nxlinkStdio();

    Thread test_thread;
    Result res = threadCreate(&test_thread, thread_test, NULL, 0x100000, 0x20, 1);
    printf("%x\n", res);
    res = threadStart(&test_thread);
    printf("%x\n", res);
    threadWaitForExit(&test_thread);

    socketExit();
}


int thread_test(void)
{
    static const AudioRendererConfig arConfig =
    {
        .output_rate     = AudioRendererOutputRate_48kHz,
        .num_voices      = 24,
        .num_effects     = 0,
        .num_sinks       = 1,
        .num_mix_objs    = 1,
        .num_mix_buffers = 2,
    };

    size_t num_channels = 1;
    size_t samplerate = 48000;
    size_t max_samples = samplerate;
    size_t max_samples_datasize = max_samples*num_channels*sizeof(opus_int16);
    size_t mempool_size = (max_samples_datasize*2 + 0xFFF) &~ 0xFFF;//*2 for 2 wavebufs.
    void* mempool_ptr = memalign(0x1000, mempool_size);
    void* tmpdata_ptr = malloc(max_samples_datasize);
    opuspkt_tmpbuf = (u8*)malloc(opuspkt_tmpbuf_size);
    opus_int16* curbuf = NULL;

    AudioDriverWaveBuf wavebuf[2] = {0};
    int i, wavei;

    HwopusDecoder hwdecoder = {0};

    AudioDriver drv;
    Result res=0;
    bool initedDriver = false;
    bool initedAudren = false;
    bool audio_playing = false;

    int opret=0;
    int total_samples_size=0;
    OggOpusFile *of = NULL;

    if (mempool_ptr) memset(mempool_ptr, 0, mempool_size);
    if (tmpdata_ptr) memset(tmpdata_ptr, 0, max_samples_datasize);
    if (opuspkt_tmpbuf) memset(opuspkt_tmpbuf, 0, opuspkt_tmpbuf_size);

    if (mempool_ptr==NULL || tmpdata_ptr==NULL || opuspkt_tmpbuf==NULL) {
        res = 1;
        printf("Failed to allocate memory.\n");
    }

    if (R_SUCCEEDED(res)) {
        res = hwopusDecoderInitialize(&hwdecoder, samplerate, num_channels);//This assumes that the opus file is <samplerate> with <num_channels>.
        if (R_FAILED(res))
            printf("hwopusDecoderInitialize: %08" PRIx32 "\n", res);
        else
        {
            res = audrenInitialize(&arConfig);
            initedAudren = R_SUCCEEDED(res);
            if (!initedAudren)
                printf("audrenInitialize: %08" PRIx32 "\n", res);
            else
            {
                printf("audren initted!\n");
                res = audrvCreate(&drv, &arConfig, 2);
                initedDriver = R_SUCCEEDED(res);
                if (R_FAILED(res))
                    printf("audrvCreate: %08" PRIx32 "\n", res);
                else
                {
                    int mpid = audrvMemPoolAdd(&drv, mempool_ptr, mempool_size);
                    audrvMemPoolAttach(&drv, mpid);

                    static const u8 sink_channels[] = { 0, 1 };
                    int sink = audrvDeviceSinkAdd(&drv, AUDREN_DEFAULT_DEVICE_NAME, 2, sink_channels);

                    res = audrvUpdate(&drv);
                    printf("audrvUpdate: %" PRIx32 "\n", res);

                    res = audrenStartAudioRenderer();
                    printf("audrenStartAudioRenderer: %" PRIx32 "\n", res);

                    audrvVoiceInit(&drv, 0, num_channels, PcmFormat_Int16, samplerate);
                    audrvVoiceSetDestinationMix(&drv, 0, AUDREN_FINAL_MIX_ID);
                    if (num_channels == 1) {//mono
                        audrvVoiceSetMixFactor(&drv, 0, 1.0f, 0, 0);
                        audrvVoiceSetMixFactor(&drv, 0, 1.0f, 0, 1);
                    }
                    else {//stereo
                        audrvVoiceSetMixFactor(&drv, 0, 1.0f, 0, 0);
                        audrvVoiceSetMixFactor(&drv, 0, 0.0f, 0, 1);
                        audrvVoiceSetMixFactor(&drv, 0, 0.0f, 1, 0);
                        audrvVoiceSetMixFactor(&drv, 0, 1.0f, 1, 1);
                    }
                    audrvVoiceStart(&drv, 0);

                    for(i=0; i<2; i++) {
                        wavebuf[i].data_raw = mempool_ptr;
                        wavebuf[i].size = max_samples_datasize*2;//*2 for 2 wavebufs.
                        wavebuf[i].start_sample_offset = i * max_samples;
                        wavebuf[i].end_sample_offset = wavebuf[i].start_sample_offset + max_samples;
                    }
                }
            }
        }
    }

    if (initedDriver)
        printf("done. Press A to play a sound.\n");
    else
        printf("Init failed.\n");

    // Main loop
    while (appletMainLoop())
    {
        hidScanInput();


        if (initedDriver)
        {
            if (!audio_playing)
            {
                //Close the opus-file if needed and (re)open it, since libopusfile doesn't support seek-to-beginning.
                if (of)
                    op_free(of);
                of = op_open_memory (sample_opus, sample_opus_size, NULL);
                if (of == NULL) {
                    printf("Failed to open OggOpusFile.\n");
                }
                else {
                    op_set_decode_callback(of, hw_decode, &hwdecoder);

                    audrvVoiceStop(&drv, 0);
                    audio_playing = true;
                }
            }

            if (audio_playing) {
                wavei = -1;
                for(i=0; i<2; i++) {
                    if (wavebuf[i].state == AudioDriverWaveBufState_Free || wavebuf[i].state == AudioDriverWaveBufState_Done) {
                        wavei = i;
                        break;
                    }
                }

                if (wavei >= 0) {
                    curbuf = (opus_int16*)(mempool_ptr + wavebuf[wavei].start_sample_offset);

                    opret = op_read(of, tmpdata_ptr, max_samples * num_channels, NULL);//The buffer used here has to be seperate from mempool_ptr.
                    if (opret < 0)
                        printf("op_read() failed: %d\n", opret);
                    else if (opret == 0) {//End of file reached (see also libopusfile docs).
                        audio_playing = false;

                        if (of)
                            op_free(of);
                        of = NULL;
                    }
                    else {
                        if (opret > max_samples) opret = max_samples;//Should never happen.
                        total_samples_size = opret*sizeof(opus_int16);//Total samples data-size per channel.
                        memcpy(curbuf, tmpdata_ptr, total_samples_size);
                        armDCacheFlush(curbuf, total_samples_size);

                        wavebuf[wavei].end_sample_offset = wavebuf[wavei].start_sample_offset + total_samples_size/sizeof(opus_int16);

                        audrvVoiceAddWaveBuf(&drv, 0, &wavebuf[wavei]);
                    }
                }
                else {
                    if (!audrvVoiceIsPlaying(&drv, 0))
                        audrvVoiceStart(&drv, 0);
                }
            }

            res = audrvUpdate(&drv);
            if (R_FAILED(res))
                printf("audrvUpdate: %" PRIx32 "\n", res);
            //if (audrvVoiceIsPlaying(&drv, 0))
            //    printf("sample count = %" PRIu32 "\n", audrvVoiceGetPlayedSampleCount(&drv, 0));
        }

        //consoleUpdate(NULL);
    }

    hwopusDecoderExit(&hwdecoder);
    if (of)
        op_free(of);
    if (initedDriver)
        audrvClose(&drv);
    if (initedAudren)
        audrenExit();

    free(mempool_ptr);
    free(tmpdata_ptr);
    free(opuspkt_tmpbuf);

    //consoleExit(NULL);
    return 0;
}

An "audio" thread shouldn't be using appletMainLoop/hidScanInput.

commented

The whole point of doing this in a thread was to avoid tying audio decoding to video/input updating.

(also tried without appleMainLoop and hidScanInput and it didn't change anything)

The whole point of doing this in a thread was to avoid tying audio decoding to video/input updating.

Ah, alright, that makes sense now.

Dunno, probably needs buffering adjustments.