Page 1 of 2

A2dp sink volume control?

Posted: Wed May 06, 2020 9:01 pm
by mooalot
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!

Re: A2dp sink volume control?

Posted: Thu May 07, 2020 3:47 am
by ESP_Vikram
Which board are you using?

Why not just change DAC volume of board instead?

Re: A2dp sink volume control?

Posted: Thu May 07, 2020 4:22 pm
by mooalot
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!

Re: A2dp sink volume control?

Posted: Mon May 11, 2020 9:15 pm
by Jakobsen
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

Re: A2dp sink volume control?

Posted: Mon May 11, 2020 10:33 pm
by mooalot
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

Re: A2dp sink volume control?

Posted: Tue May 12, 2020 7:08 pm
by mooalot
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.

Re: A2dp sink volume control?

Posted: Sat Jul 25, 2020 6:37 pm
by sdourmashkin
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

Re: A2dp sink volume control?

Posted: Sun Jul 26, 2020 6:43 pm
by mooalot
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

Re: A2dp sink volume control?

Posted: Fri Aug 14, 2020 12:44 am
by sdourmashkin
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

Re: A2dp sink volume control?

Posted: Fri Aug 14, 2020 1:49 pm
by mooalot
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. :)