kmamal / node-sdl

🎮 SDL bindings for Node.js
https://www.npmjs.com/package/@kmamal/sdl
MIT License
120 stars 10 forks source link

Change audio volume #13

Closed LmanTW closed 1 year ago

LmanTW commented 1 year ago

Is there a way to adjust the volume without going through ffmpeg? I want to change volume while playing audio

zaun commented 1 year ago

@LmanTW - In SDL2.0 there is no codec, mixer or anything like that, its only a low level x-platform api access to the audio device. In the example ffmpeg is providing all the higher level codec and mixing.

It would be possible to create a sdl-mixer binding to replace using ffmpeg, but I'm not sure @kmamal would want to add/bundle those bindings into this project.

kmamal commented 1 year ago

@LmanTW changing the volume of audio means simply multiplying each sample with the desired volume (so to raise the volume by 50% you would multiply each sample by 1.5). You do this multiplication after you have gotten the samples from ffmpeg, but before passing the data to enqueue(). The modified example would look like:

let index = 0
while (index < buffer.length) {
    const sample = audioInstance.readSample(buffer, index)
    index = audioInstance.writeSample(buffer, sample * 1.5, index)
}

audioInstance.enqueue(buffer)
audioInstance.play()

This will take care of making a one-off adjustment to the entire audio's volume level. From your description it sounds like you want to have a volume slider in your application and adjust audio levels dynamically? That would take a bit more code to achieve. The main problem is that once you pass data to enqueue() it can no longer be modified. If you enqueue 1 minute of audio then that's what you'll be hearing for the next minute, unless you call clearQueue(). What you want is to break up the audio into chunks and then progressively feed it to the instance in real time as the queued level runs low. Here's some code that could do that:

const numChunkSamples = audioInstance.buffered * channels
const numChunkBytes = numChunkSamples * audioInstance.bytesPerSample
let remaining = buffer

const interval = setInterval(() => {
    if (audioInstance.queued > numChunkBytes) { return }

    const chunk = remaining.slice(0, numChunkBytes)
    if (chunk.length === 0) {
        clearInterval(interval)
        return
    }

    // Adjust volume of chunk here

    audioInstance.enqueue(chunk)
    remaining = remaining.slice(numChunkBytes)
}, 0)

audioInstance.play()

Also keep in mind that adjusting the volume of audio can result in clipping if your sample values go above the maxSampleValue of the instance (or below the minSampleValue).

LmanTW commented 1 year ago

cool! But why is the audio choppy when I use this code to test

kmamal commented 1 year ago

@LmanTW Audio crackling and popping happens when there is a sudden change in the audio level. This usually happens for one of two reasons:

1) Buffer underrun. This one happens when you are not feeding data fast enough to the instance. Without any new data, the driver will just insert zeroes, which is probably a huge difference from the existing audio level. It looks kinda like this:

  |<-------------buffer1-------------->|<--gap-->|<-----buffer2----->

+1|****                         *******
  |    ***                   ***                  ***
  |       *                 *                        *
 0|--------*---------------*-----------***********----*--------------
  |         *             *                            *
  |          ***       ***                              ***       ***
-1|             *******                                    *******

Notice the big "cliffs" when the gap starts and ends. This is what causes the audible crackling. To avoid this make sure you are calling enqueue() before audioInstance.queued runs out.

2) Abrupt transition. This one can happen when you have large changes from one buffer to the next. For example this could happen when you adjust the volume rapidly and the consecutive buffers have large volume differences. It can also happen when you suddenly stop one audio source and abruptly switch to another. It looks kinda like this:

  |<-------------buffer1-------------->|<----------buffer2--------->

+1|****                         *******
  |    ***                   ***           *       *       *       *
  |       *                 *             * *     * *     * *     * 
 0|--------*---------------*-------------*---*---*---*---*---*---*--
  |         *             *             *     * *     * *     * *   
  |          ***       ***             *       *       *       *    
-1|             *******                

Again notice the abrupt change in audio level when switching from the sine wave to the saw wave. This causes crackling same as above.

Without seeing your code it's hard to tell what exactly is causing the problem. The example code I posted above did not cause crackling for me but then again I didn't test it too hard. If you need help you can always upload your code to a repo and let me have a look at it.

LmanTW commented 1 year ago

cool! thx

kmamal commented 1 year ago

Closing this as completed. Let me know if I should reopen it.