AudioPlaySerialflashRaw with pitch control, CPU usage issue

Status
Not open for further replies.

M4ngu

Well-known member
Hi there,
I've adapted the ResamplingSdReader from @Moo to the serial flash raw player,
it works but with 8 players running I'm getting very high values when measuring the CPU with AudioProcessorUsageMax(), using teensy 4.1 btw,
any suggestion about how to reduce the CPU load?

here's the code:
play_serialflash_raw2.h
Code:
#ifndef play_serial_raw2_h_
#define play_serial_raw2_h_

#include "Arduino.h"
#include <AudioStream.h>
#include <SerialFlash.h>

#include "stdint.h"
#include "ResamplingFlashReader.h"

class AudioPlaySerialflashRaw2 : public AudioStream
{
public:
	AudioPlaySerialflashRaw2(void) : 
		AudioStream(0, NULL),
		rawReader()
		{ 
			begin(); 
		}

	void begin(void);
	bool play(const char *filename);
	void stop(void);
	bool isPlaying(void) { return playing; }
	uint32_t positionMillis(void);
	uint32_t lengthMillis(void);
	virtual void update(void);

	void setPlaybackRate(float f) {
        rawReader.setPlaybackRate(f);
    }

    float playbackRate(float f) {
        return rawReader.playbackRate();
    }

    bool interpolationEnabled() {
        return rawReader.interpolationEnabled();
    }

    void setInterpolationEnabled(bool enableInterpolation) {
        rawReader.setInterpolationEnabled(enableInterpolation);
    }

private:

	uint32_t file_size;
	volatile uint32_t file_offset;
	volatile bool playing;

	ResamplingFlashReader rawReader;
};

#endif
play_serialflash_raw2.cpp
Code:
#include <Arduino.h>
#include "play_serialflash_raw2.h"
#include "spi_interrupt.h"


void AudioPlaySerialflashRaw2::begin(void)
{
	playing = false;
	file_offset = 0;
	file_size = 0;
}

bool AudioPlaySerialflashRaw2::play(const char *filename)
{
	stop();
    playing = rawReader.play(filename);
    return playing;
}

void AudioPlaySerialflashRaw2::stop(void)
{
	rawReader.stop();
}

void AudioPlaySerialflashRaw2::update(void)
{
	unsigned int i, n;
	audio_block_t *block;

	// only update if we're playing
	if (!playing) return;

	// allocate the audio blocks to transmit
	block = allocate();
	if (block == NULL) return;

	if (rawReader.available()) {
		// we can read more data from the file...
		n = rawReader.read(block->data, AUDIO_BLOCK_SAMPLES*2);
		file_offset += n;
		for (i=n/2; i < AUDIO_BLOCK_SAMPLES; i++) {
			block->data[i] = 0;
		}
		transmit(block);
	} else {
		rawReader.close();
		AudioStopUsingSPI();
		playing = false;
		//Serial.println("Finished playing sample");		//TODO
	}
	release(block);
}

#define B2M (uint32_t)((double)4294967296000.0 / AUDIO_SAMPLE_RATE_EXACT / 2.0) // 97352592

uint32_t AudioPlaySerialflashRaw2::positionMillis(void)
{
	return ((uint64_t)file_offset * B2M) >> 32;
}

uint32_t AudioPlaySerialflashRaw2::lengthMillis(void)
{
	return ((uint64_t)file_size * B2M) >> 32;
}
resamplingFlashReader.h
Code:
#ifndef TEENSYAUDIOLIBRARY_RESAMPLINGFLASHREADER_H
#define TEENSYAUDIOLIBRARY_RESAMPLINGFLASHREADER_H

#include "stdint.h"
#include <SD.h>
#include <SerialFlash.h>
#include <AudioStream.h>

#include "../spi_interrupt.h"

const unsigned int ResamplingFlashReader_NUM_BUFFERS = 4;

class ResamplingFlashReader {
public:
    ResamplingFlashReader() {

        for (uint i=0; i<ResamplingFlashReader_NUM_BUFFERS; i++ ) {
            _buffers[i] = NULL;
        }
    }
    void begin(void);
    bool play(const char *filename);
    void stop(void);
    bool isPlaying(void) { return _playing; }

    int read(void *buf, uint16_t nbyte);
    bool readNextValue(int16_t *value);

    void setPlaybackRate(double f) {
        _playbackRate = f;
    }

    float playbackRate() {
        return _playbackRate;
    }

    int available(void);

    void close(void);

    bool interpolationEnabled() {
        return _enable_interpolation;
    }

    void setInterpolationEnabled(bool enableInterpolation) {
        _enable_interpolation = enableInterpolation;
    }

private:
    volatile bool _playing = false;
    volatile uint32_t _file_offset;
    volatile int32_t _last_read_offset = 0;

    bool _enable_interpolation = true;
    uint32_t _file_size;
    double _playbackRate = 1.0;
    double _remainder = 0.0;

    uint _bufferPosition = 0;

    int16_t *_buffers[AUDIO_BLOCK_SAMPLES];
    unsigned int _bufferLength[ResamplingFlashReader_NUM_BUFFERS];
    bool bufferIsAvailableForRead[ResamplingFlashReader_NUM_BUFFERS];

    volatile signed char _readBuffer = -1;
    volatile signed char _playBuffer = -1;
    signed char _numBuffers = 0;
    SerialFlashFile _file;

    void updateBuffers(void);
    bool isInRange(void);

};


#endif //TEENSYAUDIOLIBRARY_RESAMPLINGSDREADER_H
resamplingFlashReader.cpp
Code:
#include "ResamplingFlashReader.h"

int ResamplingFlashReader::read(void *buf, uint16_t nbyte) {
    if (!_playing) return 0;

    unsigned int count = 0;
    int16_t *index = (int16_t*)buf;
    for (int i=0; i< nbyte/2; i++) {

        if (readNextValue(index))
            count+=2;
        else
            return count;

        index++;
    }
    return count;
}

bool ResamplingFlashReader::readNextValue(int16_t *value) {

    if (_bufferLength[_playBuffer] == 0)
        return false;

    if (_playbackRate > 0 ) {
        //forward playback

        if (_numBuffers == 1 && _last_read_offset + _bufferPosition > _file_size)
            return false;

        //Serial.printf("readNextValue: %d/%d \n", _bufferPosition, _bufferLength[_playBuffer]);

        if (_bufferPosition >= _bufferLength[_playBuffer]) {
            bufferIsAvailableForRead[_playBuffer] = true;
            //Serial.printf("\ndiscard buffer: %d\n", _playBuffer);

            if (_numBuffers ==1 && _file_offset >= _file_size)
                return false;

            _numBuffers--;
            //Serial.printf("num  buffers: %d\n", _numBuffers);

            if (_playBuffer < _numBuffers) {
                _readBuffer = _playBuffer;
                //Serial.printf("switch read buffer to: %d\n", _readBuffer);
            }

            _bufferPosition -= _bufferLength[_playBuffer];

            _playBuffer++;
            if (_playBuffer >= ResamplingFlashReader_NUM_BUFFERS) {
                _playBuffer = 0;
            }
            //Serial.printf("switch play buffer to: %d\n", _playBuffer);
            updateBuffers();
        }
    } else if (_playbackRate < 0) {

        if (_numBuffers == 1)
            if (_file_offset + (AUDIO_BLOCK_SAMPLES * 2) + _bufferPosition < 0)
                return false;

        // reverse playback
        if (_bufferPosition < 0) {

            if (_numBuffers == 0)
                return false;

            bufferIsAvailableForRead[_playBuffer] = true;
            _readBuffer = _playBuffer;

            _numBuffers--;

            _playBuffer ++;
            if (_playBuffer >= ResamplingFlashReader_NUM_BUFFERS) {
                _playBuffer = 0;
            }

            _bufferPosition += _bufferLength[_playBuffer];
            updateBuffers();

        } else if (_bufferPosition > (AUDIO_BLOCK_SAMPLES-1) * 2) {
            _bufferPosition = (AUDIO_BLOCK_SAMPLES-1) * 2;
        }
    }
    int16_t *_buffer = _buffers[_playBuffer];
    int16_t result = _buffer[_bufferPosition/2];
    //Serial.printf("r: %d,", result);

    if (_enable_interpolation) {
        if (_remainder != 0.0) {
            if (_playbackRate > 0.0) {
                int16_t next;
                if (_bufferPosition < _bufferLength[_playBuffer]-2) {
                    next =_buffer[(_bufferPosition/2)+1];
                } else {
                    int nextPlayBuffer = _playBuffer == ResamplingFlashReader_NUM_BUFFERS - 1? 0 : _playBuffer+1;

                    if (_last_read_offset + _bufferPosition < _file_size-2) // second interpolation point is before the end of the file, don't int
                        next = _buffers[nextPlayBuffer][0];
                    else
                        next = result; // dont interpolate past the end of the file
                }
                int16_t interpolated = static_cast<int16_t>( (((1-_remainder) * 1000.0 * result) + (_remainder * 1000.0 * next)) / 1000.0 );
                result = interpolated;
            } else if (_playbackRate < 0.0) {
                int16_t prev;
                if (_bufferPosition >= 2) {
                    prev = _buffer[(_bufferPosition/2)-1];
                } else {
                    if (_file_offset > 2) {
                        int nextPlayBuffer = _playBuffer == ResamplingFlashReader_NUM_BUFFERS - 1 ? 0 : _playBuffer + 1;
                        int nextBufLength = _bufferLength[nextPlayBuffer] / 2;
                        prev = _buffers[nextPlayBuffer][nextBufLength - 1];
                    } else
                        prev = result;
                }
                int16_t interpolated = static_cast<int16_t>( (((1 + _remainder) * 1000.0 * result) + (-_remainder * 1000.0 * prev)) / 1000.0 );
                result = interpolated;
            }
        }
    }

    _remainder += _playbackRate;

    auto delta = static_cast<signed int>(_remainder);
    _remainder -= static_cast<double>(delta);

    _bufferPosition += 2 * delta;
    *value = result;
    return true;
}

void ResamplingFlashReader::begin(void)
{
    _playing = false;
    _file_offset = 0;
    _file_size = 0;
}

bool ResamplingFlashReader::play(const char *filename)
{
    stop();

    AudioStartUsingSPI();
    __disable_irq();
    _file = SerialFlash.open(filename);
    __enable_irq();

    if (!_file) {
        AudioStopUsingSPI();
        return false;
    }

    __disable_irq();
    _file_size = _file.size();
    __enable_irq();

    if (_playbackRate < 0) {
        // reverse playback - forward _file_offset to last audio block in file
        _file_offset = _file_size - AUDIO_BLOCK_SAMPLES * 2;
    } else {
        // forward playabck - set _file_offset to first audio block in file
        _file_offset = 0;
    }


    for (char i=0; i<ResamplingFlashReader_NUM_BUFFERS; i++ ) {
        _buffers[i] = (int16_t*)malloc( AUDIO_BLOCK_SAMPLES * 2);
        bufferIsAvailableForRead[i] = true;

    }
    _readBuffer = 0;
    _numBuffers = 0;
    updateBuffers();
    _playBuffer = 0;
    if (_numBuffers == 0)
        return false;

    _playing = true;

    if (_playbackRate < 0) {
        _bufferPosition = _bufferLength[_playBuffer]-2;
    }

    return true;
}

void ResamplingFlashReader::stop()
{
    __disable_irq();
    if (_playing) {
        _playing = false;
        __enable_irq();
        _file.close();
        AudioStopUsingSPI();
        //StopUsingSPI();

        for (uint i=0; i<ResamplingFlashReader_NUM_BUFFERS; i++ ) {
            if (_buffers[i] != NULL) {
                free(_buffers[i]);
                _buffers[i] = NULL;
            }
        }
    } else {
        __enable_irq();
    }
}

int ResamplingFlashReader::available(void) {
    return _file.available() / _playbackRate;
}

void ResamplingFlashReader::close(void) {
    if (_playing)
        stop();
    _file.close();
}

void ResamplingFlashReader::updateBuffers() {

    bool forward = (_playbackRate >= 0.0);

    while (_readBuffer >= 0
           && isInRange()
           && bufferIsAvailableForRead[_readBuffer]) {

        //Serial.printf("read buffer: %d (_numBuffers:%d) \n",_readBuffer, _numBuffers);
        int16_t *buffer = _buffers[_readBuffer];
        bufferIsAvailableForRead[_readBuffer] = false;

        _last_read_offset = _file_offset;
        uint16_t numberOfBytesToRead = AUDIO_BLOCK_SAMPLES * 2;
        if (!forward) {
            if (_file_offset < 0) {
                // reverse playback, last buffer, only read partial remaining buffer that hasn't already played
                numberOfBytesToRead = _file_offset + AUDIO_BLOCK_SAMPLES * 2;
            }
            _file.seek(_file_offset < 0? 0 : _file_offset);
        }
        //Serial.printf("\nreading %d bytes, starting at:%d (into readbuff %d) - _file_offset:%d\n", numberOfBytesToRead, _file.position(), _readBuffer, _file_offset);
        int numRead = _file.read(buffer, numberOfBytesToRead);
        _bufferLength[_readBuffer] = numRead;
        //Serial.printf("read %d bytes\n", numRead);

        if (_playbackRate < 0) {
            _file_offset -= numRead;
        } else
            _file_offset += numRead;

        if (numRead > 0) {
            _readBuffer ++;
            if (_readBuffer >= ResamplingFlashReader_NUM_BUFFERS)
                _readBuffer = -1;

            _numBuffers++;
        } else {
            _readBuffer = -1;
            //Serial.printf("\n Not able to read anymore buffers for the moment...\n", numRead);
        }
    }
}

bool ResamplingFlashReader::isInRange() {
    if (_playbackRate >= 0.0) {
        return _file_offset < _file_size;
    } else {
        return _file_offset + AUDIO_BLOCK_SAMPLES * 2 > 0;
    }
}
 
Serial flash is - compared to other memory - incredible slow.
I don't think there is a way to make it much faster.
 
Hi Frank, thanks for the input but when using the original flash raw player the AudioProcessorUsageMax() was like 31% and with the resampled version is 102%
also, that 31% isn't only about the 8 flash players, there are lots of filters, effects, mixers,
indeed the CPU when using the AudioPlayMemory is 19%, so using using the raw flash player, instead the memory one, increases the whole thing about +12% and then using the resampled version gives +71%
that is too much difference, isn't it?
 
I've also try to set the interpolation off, it does not affect too much the CPU usage...and sounds worse

pitching down the samples has a small impact in the CPU, looks like the issue comes when pitching the samples up,
which makes sense because much more samples has to be acquired from the flash chip
 
Status
Not open for further replies.
Back
Top