Homebrew Development

Discussion in '3DS - Homebrew Development and Emulators' started by aliak11, Jan 16, 2014.

  1. nop90

    nop90 GBAtemp Maniac

    Member
    1,358
    2,039
    Jan 11, 2014
    Italy
    Rome
    The main problem should be that reading from sd is very slow (slower with 3dsx executable than with code installed from a CIA). It's better lo load in memory a compressed sound file (ogg, mp3, ...) and decode it chunk by chunk when needed.

    Other things I learned working on the audio driver of the SDL lib are:
    - The audio buffer feeding loop should be in a separate thread running with higher priority than the program code
    - If you use the GPU, the drawing code needs to run on a therad with higher priority than the sound thread to avoid flickering
    - never run threads with the highest priority (0x18), or you'll interfere with the NDSP handler thread started by ctrulib calling ndspInit();
     


  2. B_E_P_I_S_M_A_N

    B_E_P_I_S_M_A_N oh no

    Member
    636
    2,229
    Jun 7, 2016
    United States
    Sixth Circle of Hell
    Alright. I tried running the audio buffer feeding loop in a separate thread with a priority 2 greater than the current thread. However, once I did this, all calls to DSP_FlushDataCache fail with an error code of 0xE0E01BF5.

    Here's the code:
    code

    and for flushing the data cache. This runs in the main thread; will this cause problems?

    code
     
  3. zoogie

    zoogie simple pimp tool

    Member
    6,158
    7,775
    Nov 30, 2014
    United States
  4. elhobbs

    elhobbs GBAtemp Advanced Fan

    Member
    762
    284
    Jul 28, 2008
    United States
    The ndsp code does not seem to like being called from a separate thread. Another issue is that the 3ds timer is not very accurate. When streaming I have found it to be beneficial to use a lot of small buffers and to measure playback position as a function of completed buffers. It is fine to use a separate thread for decoding and filling buffers but let the main thread submit them. And if you do use a thread then make sure it sleeps.
     
  5. B_E_P_I_S_M_A_N

    B_E_P_I_S_M_A_N oh no

    Member
    636
    2,229
    Jun 7, 2016
    United States
    Sixth Circle of Hell
    Alright; so the way I see it, the audio buffer feeding should be run at a higher priority than the main thread, but all of the ndsp functions should be called from the main thread itself?

    So, hypothetically, let's set a variable:
    Code:
    bool readNextBuffer[24];
    
    and let's define the function in which the buffer is read as
    Code:
    void updateBuffers(int channel) {
            if (readNextBuffer) {
                // swap buffers, read next buffer into memory
                readNextBuffer[channel] = false;
            }
    }
    
    We run updateBuffers() in a thread, like so:
    Code:
    Thread bufferThreads
    for i in range(24):
        Thread bufferThread = threadCreate(updateBuffers, i, STACKSIZE, prio + 1, -2, false);
    
    We also have some code which handles all the ndsp functions, which runs on the main thread:
    Code:
    void checkBuffers() {
        for (int i = 0; i < 24; i++) {
            // read current stream into memory
            if (stream->prevWaveBuf == NULL && stream->nextWaveBuf != NULL && ndspChnGetWaveBufSeq(i) != stream->nextWaveBuf->sequence_id && stream->eof == true) {
                readNextBuffer[i] = true
                threadJoin(bufferThread);
    
                DSP_FlushDataCache();
                ndspChnWaveBufAdd();
            }
        }
    }
    
    So, essentially, if the checkBuffers function (run in the main thread) detects that the next chunk should be loaded into memory, it sets the readNextBuffer flag to true and waits for the next chunk to be loaded into memory, then makes all the necessary ndsp calls back in the main thread. Should this work? I'm rather new to threading, so I'm not sure if this approach is safe or not. Also, where would sleeping for each thread fall into this?
     
  6. elhobbs

    elhobbs GBAtemp Advanced Fan

    Member
    762
    284
    Jul 28, 2008
    United States
    24 threads? No. Keep in mind the 3ds uses preemptive multitasking.
    Certainly not the cleanest code or even the best approach, but here is an example. The dsp_* functions deal with handling a streaming buffer. Which in this case is software mixed sound effects.
    https://github.com/elhobbs/hexen/blob/master/3ds/3ds_sound.c

    And again here for mus synthesis. This example does use a thread but you will see a few extra sleeps thrown in to get it to play nice(-ish).
    https://github.com/elhobbs/hexen/blob/master/3ds/musplay.c (toward the bottom)
     
  7. agoidz

    agoidz Newbie

    Newcomer
    3
    1
    Aug 10, 2017
    Philippines
    Hi 3ds have OTG???
     
  8. trainboy2019

    trainboy2019 Shadow Maker in the Shadows

    Member
    737
    210
    Oct 6, 2015
    Antarctica
    Ylisse
    That's a lot of quotes! If you mean USB On-The-Go, no. It's impossible because the 3ds has no usb support.
     
  9. nop90

    nop90 GBAtemp Maniac

    Member
    1,358
    2,039
    Jan 11, 2014
    Italy
    Rome
    Sorry for aswering so late, I'm not at home and can connect only at night with my mobile.

    About threads priority, the lower the priority value, the higher the thread priority.

    So you have to subtract something (not adding) to the value you get with svcGetThreadPriority().

    This should solve the exception caused flushing memory from the created thread (I don't have problems calling dsp functions from other theads than the main one)

    Now I don't have time to check all your code, but let me know if with this change something works better.
     
  10. elhobbs

    elhobbs GBAtemp Advanced Fan

    Member
    762
    284
    Jul 28, 2008
    United States
    In regards to threads - if you stop a channel and then try to play a new sound on that same channel from a thread then it is not going to start correctly unless you put sleep call in between the stop and start.
     
  11. nop90

    nop90 GBAtemp Maniac

    Member
    1,358
    2,039
    Jan 11, 2014
    Italy
    Rome
    Maybe the hint is for @B_E_P_I_S_M_A_N

    In my code usually I use only a channel with two buffer, filling a buffer with new samples while the other is playing and then adding it to the channel queue.

    I don't need to stop the channel since there are samples to play.
     
  12. B_E_P_I_S_M_A_N

    B_E_P_I_S_M_A_N oh no

    Member
    636
    2,229
    Jun 7, 2016
    United States
    Sixth Circle of Hell
    Actually, looking back, I'm getting a bytePerSample value of 0, which can't be right. This is probably why the DSP_FlushDataCache call is failing. I'll look into this.

    And yes, the sound file is started and stopped from the main thread, and the higher-priority thread updates it. The channel is not being explicitly stopped while streaming, though (i.e. ndspChnWaveBufClear()).
     
  13. elhobbs

    elhobbs GBAtemp Advanced Fan

    Member
    762
    284
    Jul 28, 2008
    United States
    Start/stop we're just examples of commands that do not take effect immediately and need to be processed by the ndsp thread. The problem I was trying to identify as that some commands won't be initiated until the calling thread releases control due to the cooperative multi-tasking nature of the 3ds thread execution.
    Sorry if I confused the issue. I was not trying to imply multiple channels were needed to stream music/sounds.
     
  14. B_E_P_I_S_M_A_N

    B_E_P_I_S_M_A_N oh no

    Member
    636
    2,229
    Jun 7, 2016
    United States
    Sixth Circle of Hell
    Oh, no, I'm only using one channel to stream music. Sorry if I caused any confusion.

    Anyways, I was able to get the audio feeding thread to work; however, the choppiness was still there, so now I'm heading in the other direction and trying to load the entire file into memory. I realize that the audio buffer to be flushed must be linear-allocated.

    Anyways, I load the entire audio section into memory like so:
    Code:
       typedef struct {
          ...
          char **chunkData;
          char *data;
       } wavFile;
    
       ...
    
        // get chunk data
       wav->chunkData = calloc( ceil(wav->fileSize / wav->chunkSize), wav->chunkSize);
       fseek(fp, 44, SEEK_SET);
       fread(wav->chunkData, wav->chunkSize, ceil(wav->fileSize / wav->chunkSize), fp);
    
    ...then I allocate memory for the main buffer, and set it equal to the first chunk in memory...
    Code:
        wav->data = (char*)linearAlloc(wav->chunkSize);
        wav->data = wav->chunkData[0];
    
    ...then I try to flush the buffer as usual...
    Code:
        ndspWaveBuf *wbuf;
      
       stopWav(channel);
       ndspChnReset(channel);
       ndspChnSetRate(channel, wav->rate);
       ndspChnInitParams(channel);
       ndspChnSetFormat(channel, NDSP_CHANNELS(wav->channels) | NDSP_ENCODING(wav->encoding));
      
       wbuf = calloc(1, sizeof(ndspWaveBuf));
      
       wbuf->data_vaddr = wav->data;
       wbuf->nsamples = wav->chunkNSamples;
       wbuf->looping = (wav->chunkSize < wav->size) ? false : loop;
      
       Result ret = DSP_FlushDataCache((u32*)wav->data, wav->chunkSize);
       ndspChnWaveBufAdd(channel, wbuf);
    
    However, doing this results in DSP_FlushDataCache failing. Am I supposed to do something with the chunk data before unloading it onto the main buffer? Because AFAIK, fread() just dumps the file data without any alterations, so i don't see why this wouldn't work.
     
    Last edited by B_E_P_I_S_M_A_N, Aug 11, 2017
  15. elhobbs

    elhobbs GBAtemp Advanced Fan

    Member
    762
    284
    Jul 28, 2008
    United States
    There are many problems here. Allocating the wrong size buffers and overwriting pointers to name a couple. Calloc does not create multiple buffers. It just multiplies size and count to get the full size and returns a single pointer to a linear region of memory for the full size - rather than an array of pointers.
     
  16. B_E_P_I_S_M_A_N

    B_E_P_I_S_M_A_N oh no

    Member
    636
    2,229
    Jun 7, 2016
    United States
    Sixth Circle of Hell
    Yeah, I realized. I opted instead to use malloc() to allocate an array of char pointers, like so...
    Code:
    wav->chunkData = malloc(sizeof(char*) * ceil(wav->fileSize / wav->chunkSize));
       for (int i = 0; i < ceil(wav->fileSize / wav->chunkSize); i++) {
           wav->chunkData[i] = malloc(wav->chunkSize);
       }
    
    ...then I read each chunk into memory like this...
    Code:
    fseek(fp, 44, SEEK_SET);
       for (int i = 0; i < (int)ceil(wav->fileSize / wav->chunkSize); i++) {
           fread(wav->chunkData[i], wav->chunkSize, 1, fp);
       }
    
    ...then, for the initial buffer, I set it to the first element in the array. I replaced the pointer override with just some basic memcpy() usage...
    Code:
    memcpy(wav->data, wav->chunkData[0], wav->chunkSize);
    
    ...then, during the audio buffer feeding loop, I read the next chunk into memory like so:
    Code:
                   stream->chunkIndex++;
                   memset(stream->nextData, 0, chunkSize);
                   memcpy(stream->nextData, wav->chunkData[stream->chunkIndex], chunkSize);
    
    ...then flush as usual.

    There's a problem however; while the initial buffer flush seems to work just fine, subsequent flushes in the audio buffer feeding loop seem to fail with a result code of 0xe0e01bf5:
    Code:
    Module:
      OS (6)
    Description:
      Invalid address (1013)
    Summary:
      Invalid argument (7)
    Level:
      Usage (28)
    
    ...so it seems that I'm passing an invalid address, which is odd, considering the buffer seemed to work fine when I was reading directly from the file. The fact that the initial flush works seems to indicate that the actual audio data is fine (I've tried passing copying other chunks other than the initial one into memory, and they seem to work fine). I tried inserting memset() to see if that would fix anything; it didn't. Is this another case of memory mismanagement? Or is it something else? Am I still allocating the wrong-sized buffers?
     
  17. elhobbs

    elhobbs GBAtemp Advanced Fan

    Member
    762
    284
    Jul 28, 2008
    United States
    It is hard to tell without seeing the full source.
    Not that this is the problem, but calling ceil on the result of integer division does not do what you are thinking it does.
     
  18. B_E_P_I_S_M_A_N

    B_E_P_I_S_M_A_N oh no

    Member
    636
    2,229
    Jun 7, 2016
    United States
    Sixth Circle of Hell
    Ah, sorry for taking so long to respond. I hadn't realized I had gotten a reply on this thread, for whatever reason.

    Anyways, here's the source: https://github.com/BEPISMAN2/sound-thing/tree/chunk-audio-feeding

    And yes, I just got what you meant on ceil(). That would be getting the ceiling value of an already-truncated integer, right?
     
  19. spinal_cord

    spinal_cord Knows his stuff

    Member
    2,978
    570
    Jul 21, 2007
    somewhere
    Can someone please tell me what aptGetStatus() has changed to in later devkitpro versions?
    I'm trying to update the following code, but as usual, there doesn't seem to be ANY documentation ANYWHERE on thse changes...
    Code:
        APT_AppStatus status;
    
        while((status=aptGetStatus()) != APP_RUNNING) {
            // Close Lid
            if(status == APP_PREPARE_SLEEPMODE)
            {
                Pause();
                aptSignalReadyForSleep();
                aptWaitStatusEvent();
                Resume();
            }
            // Home Button
            if(status == APP_SUSPENDING)
            {
                Pause();
                aptReturnToMenu();
                Resume();
            }
           
            if(aptGetStatus() == APP_EXITING)
            {
    
                Pause(); // exit audio safely..
    
                quit_thyself=1;
                break;
            }
    
        }
    Thanks.
     
  20. nop90

    nop90 GBAtemp Maniac

    Member
    1,358
    2,039
    Jan 11, 2014
    Italy
    Rome
    apt status events are now handled inside aptMainLoop(), so you can now simply use a

    Code:
    while (aptMainLoop()) {
     // your main loop here
    }
    
    this works fine for 3dsx executable. For programs released in CIA format you need to handle the home button and lid close.

    For this look here.