A2dp sink volume control?

mooalot
Posts: 13
Joined: Sun Jan 26, 2020 4:31 am

A2dp sink volume control?

Postby mooalot » Wed May 06, 2020 9:01 pm

Hey everyone!

I have been working on getting the volume control to work. The example code only gets and sets the local variable volume but does not actually change the output volume when played on a speaker. Does anybody know how to successfully change the volume?? Because the I2S is using PCM in this example, I attempted to change the output volume by bit shifting the data depending on the volume percentage (which is already a feature in the a2dp_sink example). The I2s transfers 2 bytes for each channel, so I thought that bit shifting two bytes of data to the right would decrease the volume, but it just makes it really noisy.

Here is code I edited in the a2dp_sink example:

Code: Select all

uint8_t masks[9] = {0x00,0x01,0x03,0x07,0x0f,0x1f,0x3f,0x7f,0xff};

size_t write_ringbuf(const uint8_t *data, size_t size)
{
    const int numAmpBytes = 2;
    uint8_t *writableData = (uint8_t*) data;
    uint8_t mask;
    uint8_t tempData;
    for (size_t h = 0; h < size; h += numAmpBytes) {
        mask = 0x00;
        for (size_t i = h; i < (h + numAmpBytes); ++i) {
            tempData = writableData[i];
            writableData[i] = writableData[i] >> (8 - master_volume);
            writableData[i] |= mask << (master_volume);
            mask = tempData & masks[8 - master_volume];
        }
    }
    BaseType_t done = xRingbufferSend(s_ringbuf_i2s, (void*) writableData , size, (portTickType)portMAX_DELAY);
    if(done){
        return size;
    } else {
        return 0;
    }
}
Does anyone know why this doesn't work? This is the only logarithmic way to decay the volume without it being to computationally heavy.

Thanks everyone!

ESP_Vikram
Posts: 25
Joined: Fri Nov 23, 2018 12:07 pm

Re: A2dp sink volume control?

Postby ESP_Vikram » Thu May 07, 2020 3:47 am

Which board are you using?

Why not just change DAC volume of board instead?

mooalot
Posts: 13
Joined: Sun Jan 26, 2020 4:31 am

Re: A2dp sink volume control?

Postby mooalot » Thu May 07, 2020 4:22 pm

Hello ESP_Vikram,

I am just using the Esp32 with the esp-WROOM32.

I would just change the dac output volume, but I am using an external 16 bit I2s decoder to get better quality.

Is there any way to change the volume with I2s that I am unaware of?

Thanks!

User avatar
Jakobsen
Posts: 89
Joined: Mon Jan 16, 2017 8:12 am

Re: A2dp sink volume control?

Postby Jakobsen » Mon May 11, 2020 9:15 pm

Hi MooaLot
Correct design practice would be control volume in as close the speaker as possible - so if your last point of control is an external DAC find out if it has a volume control to offer over I2C.
If that is not the case you are back to apply you volume to you PCM sample before you pass them on to the I2S DMA driver layer. No I2S stuff for that.

/jørgen
Analog Digital IC designer / DevOps @ Merus Audio, Copenhagen, Denmark.
We do novel and best in class Audio amplifiers for consumer products.
Programmed assembler for C-64 back in 1980's, learned some electronics - hacking since then

mooalot
Posts: 13
Joined: Sun Jan 26, 2020 4:31 am

Re: A2dp sink volume control?

Postby mooalot » Mon May 11, 2020 10:33 pm

Hey Jakobsen!

Thanks for the response! I may have asked the incorrect question. When the callback function bt_app_a2d_data_cb() is called, it reads PCM formatted data from static memory. That data is then sent to a ring buffer. To modify the volume of this stream, I am uncertain what to do. I have tried bit shifting the data and many other variations of modification to try and change the volume. Any change that I make results in unwanted noise, making the music inaudible.

The following code is what I added to modify the data before it is sent to the ring buffer. Essentially it just shifts a certain number of bytes (which is determined by numBytesShifted) depending on the volume level. I am unable to determine how to properly shift the data. I think it may be because I dont know how many bytes per second I am receiving by bluetooth.

Code: Select all

uint8_t masks[9] = {0x00,0x01,0x03,0x07,0x0f,0x1f,0x3f,0x7f,0xff};

size_t write_ringbuf(const uint8_t *data, size_t size)
{
    The following code is an attempt to change the volume using bit shifting.
    const int numBytesShifted = 2;
    uint8_t* writableData = (uint8_t*) data;
    uint8_t mask;
    uint8_t tempData;
    for (size_t h = 0; h < size; h += numAmpBytes) {
        mask = 0x00;
        for (size_t i = h; i < (h + numAmpBytes); ++i) {
            tempData = writableData[i];
            writableData[i] = writableData[i] >> (8 - master_volume);
            writableData[i] |= mask << (master_volume);
            mask = tempData & masks[8 - master_volume];
        }
    }

    if(xRingbufferSend(s_ringbuf_i2s, (void*) data, size, (portTickType)portMAX_DELAY)){
        return size;
    } else {
        return 0;
    }
}
Any help be great! I have been stumped for weeks

mooalot
Posts: 13
Joined: Sun Jan 26, 2020 4:31 am

Re: A2dp sink volume control?

Postby mooalot » Tue May 12, 2020 7:08 pm

Nevermind! I fixed the problem! Volume control works now. The PCM data is received as most significant bit first and least significant byte first for each channel.

sdourmashkin
Posts: 3
Joined: Sat Jul 25, 2020 6:33 pm

Re: A2dp sink volume control?

Postby sdourmashkin » Sat Jul 25, 2020 6:37 pm

Hi mooalot,

Could you please share your code in write_ringbuf that fixed this? I'm having the same problem not being able to control volume. I'm using the internal DAC with .communication_format = I2S_COMM_FORMAT_PCM in the i2s_config.

Thanks,
Steven

mooalot
Posts: 13
Joined: Sun Jan 26, 2020 4:31 am

Re: A2dp sink volume control?

Postby mooalot » Sun Jul 26, 2020 6:43 pm

Hey Steven,

So here is the code I use to change the volume. Its in its own file for utilities, but you can put it wherever you want.

Code: Select all

//the following code changes the volume
uint8_t *volume_control_changeVolume(uint8_t *data, uint8_t *outputData, size_t size, uint8_t volume) {
    const int numBytesShifted = 2;
    int16_t pcmData;
    bool isNegative;
    memcpy(outputData, data, size);
    size_t h = 0;
    for (h = 0; h < size; h += numBytesShifted) {
        pcmData = ((uint16_t) data[h + 1] << 8) | data[h];
        isNegative = pcmData & 0x80;
        if (isNegative) 
            pcmData = (~pcmData) + 0x1;
        pcmData = pcmData >> (16 - volume);
        if (isNegative) 
            pcmData = (~pcmData) + 0x1;
        outputData[h+1] = pcmData >> 8;
        outputData[h] = pcmData;
    }
    return outputData;
}

My write ringbuff function looks like this

Code: Select all

size_t write_ringbuf(const uint8_t *data, size_t size)
{
    uint8_t *volumedData = (uint8_t *)malloc(sizeof(uint8_t)*size);
    if(xRingbufferSend(s_ringbuf_i2s, (void*) volume_control_changeVolume(data, volumedData, size, master_volume) , size, (portTickType)portMAX_DELAY)){
        free(volumedData);
        return size;
    } else {
        free(volumedData);
        return 0;
    }
}
Master volume is a uint8_t that is what determines volume. Here is my code where I put that.

Code: Select all

static void volume_set_by_controller(uint8_t volume)
{
    ESP_LOGI(BT_RC_TG_TAG, "Volume is set by remote controller %d%%\n", (uint32_t)volume * 100 / 0x7f);
    _lock_acquire(&s_volume_lock);
    s_volume = volume;
    _lock_release(&s_volume_lock);
    master_volume = (uint8_t)((s_volume / 127.0) * 16.0 + .5);
    ESP_LOGI(BT_RC_TG_TAG, "Mastervolume is %d\n", master_volume);
    esp_avrc_rn_param_t rn_param;
    rn_param.volume = volume;

    // the thing that changes the volume
    int response = esp_avrc_tg_send_rn_rsp(ESP_AVRC_RN_VOLUME_CHANGE, ESP_AVRC_RN_RSP_CHANGED, &rn_param);
    ESP_LOGI(BT_RC_TG_TAG, "the response is %d", response);
}

Also make sure your I2S setup has

Code: Select all

.communication_format = I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB,
This is all for the a2dp sink.

Hope this saves you some time, let me know if you have any other questions! :D

sdourmashkin
Posts: 3
Joined: Sat Jul 25, 2020 6:33 pm

Re: A2dp sink volume control?

Postby sdourmashkin » Fri Aug 14, 2020 12:44 am

Hi mooalot,

Thank you for sharing your code - it's very helpful!

I implemented your volume control functions but the issue is that I don't hear any audio when I make the I2S setup as:

Code: Select all

.communication_format = I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB,
When I set it to I2S_COMM_FORMAT_PCM, I can hear the audio, but the volume doesn't seem to be changing (although I can confirm that master_volume is being changed), and the signal is much noisier. Note, I'm already using a fix for 8-bit audio as described here: https://www.esp32.com/viewtopic.php?t=6984

Do you have any idea what might we wrong with my I2S setup? Note that as mentioned before I'm trying to use the internal DAC on the esp32 to output audio (rather than external DAC via I2S).

Thanks so much for your time!

- Steven

mooalot
Posts: 13
Joined: Sun Jan 26, 2020 4:31 am

Re: A2dp sink volume control?

Postby mooalot » Fri Aug 14, 2020 1:49 pm

Hi Steven,

I'm sorry! I didn't read the portion about the internal dac, my bad.

So when you configure your I2S just as the espressif website says:

Code: Select all

static const i2s_config_t i2s_config = {
    .mode = I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN,
    .sample_rate = 44100,
    .bits_per_sample = 16, /* the DAC module will only take the 8bits from MSB */
    .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
    .intr_alloc_flags = 0, // default interrupt priority
    .dma_buf_count = 8,
    .dma_buf_len = 64,
    .use_apll = false
};
(if the code above doesnt work after testing the rest of my suggestions, try adding ".communication_format = I2S_COMM_FORMAT_I2S_MSB," to it.)

Because your volume data is only the 8 MSB, you only need to change one number from the previous function I gave you. The change is on the 13th line below. (16 -> 8)

Code: Select all

//the following code changes the volume
uint8_t *volume_control_changeVolume(uint8_t *data, uint8_t *outputData, size_t size, uint8_t volume) {
    const int numBytesShifted = 2;
    int16_t pcmData;
    bool isNegative;
    memcpy(outputData, data, size);
    size_t h = 0;
    for (h = 0; h < size; h += numBytesShifted) {
        pcmData = ((uint16_t) data[h + 1] << 8) | data[h];
        isNegative = pcmData & 0x80;
        if (isNegative) 
            pcmData = (~pcmData) + 0x1;
        pcmData = pcmData >> (8 - volume);
        if (isNegative) 
            pcmData = (~pcmData) + 0x1;
        outputData[h+1] = pcmData >> 8;
        outputData[h] = pcmData;
    }
    return outputData;
}
Then make sure that the master volume can only go to 8, you can do this like so:

Code: Select all

master_volume = (uint8_t)((s_volume / 127.0) * 8.0 + .5);
I just made most of this up without testing, but it should work.
Post a reply and let me know if this worked (so others may reference this), otherwise let me know and ill pull out my esp32 and figure it out. :)

Who is online

Users browsing this forum: No registered users and 11 guests