diff --git a/README.md b/README.md index e244a27..5840c77 100644 --- a/README.md +++ b/README.md @@ -25,11 +25,22 @@ Other options: * -r - Loops the playback After transmission has begun, simply tune an FM receiver to chosen frequency, You should hear the playback. +### Raspberry Pi 4 +On Raspberry Pi 4 other built-in hardware probably interfers somehow with this software making transmitting not possible on all standard FM broadcasting frequencies. In this case it is recommended to: +1. Compile executable with option to use GPIO21 instead of GPIO4 (PIN 40 on GPIO header): +``` +make GPIO21=1 +``` +2. Change either ARM core frequency scaling governor settings to "performance" or to change ARM minium and maximum core frequencies to one constant value (see: https://www.raspberrypi.org/forums/viewtopic.php?t=152692 ). +``` +echo "performance"| sudo tee /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor +``` +3. Using lower FM broadcasting frequencies (below 93 MHz) when transmitting. ### Supported audio formats You can transmitt uncompressed WAV (.wav) files directly or read audio data from stdin, eg.: ``` sudo apt-get install sox -sox star_wars.wav -r 22050 -c 1 -b 16 -t wav - | sudo ./fm_transmitter -f 100.6 - +sox acoustic_guitar_duet.wav -r 22050 -c 1 -b 16 -t wav - | sudo ./fm_transmitter -f 100.6 - ``` Please note only uncompressed WAV files are supported. If you receive the "corrupted data" error try converting the file, eg. by using SoX: ``` diff --git a/main.cpp b/main.cpp index cd49a40..c447db4 100644 --- a/main.cpp +++ b/main.cpp @@ -1,7 +1,7 @@ /* - fm_transmitter - use Raspberry Pi as FM transmitter + FM Transmitter - use Raspberry Pi as FM transmitter - Copyright (c) 2019, Marcin Kondej + Copyright (c) 2020, Marcin Kondej All rights reserved. See https://github.com/markondej/fm_transmitter @@ -37,21 +37,21 @@ #include #include -bool play = true; -Transmitter *transmitter = NULL; +bool stop = false; +Transmitter *transmitter = nullptr; void sigIntHandler(int sigNum) { - if (transmitter != NULL) { + if (transmitter != nullptr) { std::cout << "Stopping..." << std::endl; - transmitter->stop(); - play = false; + transmitter->Stop(); + stop = true; } } int main(int argc, char** argv) { - float frequency = 100.f, bandwidth = 100.f; + float frequency = 100.f, bandwidth = 200.f; uint16_t dmaChannel = 0; bool showUsage = true, loop = false; int opt, filesOffset; @@ -87,27 +87,33 @@ int main(int argc, char** argv) signal(SIGINT, sigIntHandler); signal(SIGTSTP, sigIntHandler); + auto finally = [&]() { + delete transmitter; + transmitter = nullptr; + }; try { - transmitter = &Transmitter::getInstance(); + transmitter = new Transmitter(); + std::cout << "Broadcasting at " << frequency << " MHz with " + << bandwidth << " kHz bandwidth" << std::endl; do { std::string filename = argv[optind++]; if ((optind == argc) && loop) { optind = filesOffset; } - WaveReader reader(filename != "-" ? filename : std::string(), play); - PCMWaveHeader header = reader.getHeader(); - std::cout << "Broadcasting at " << frequency << " MHz with " - << bandwidth << " kHz bandwidth" << std::endl; - std::cout << "Playing: " << reader.getFilename() << ", " + WaveReader reader(filename != "-" ? filename : std::string(), stop); + WaveHeader header = reader.GetHeader(); + std::cout << "Playing: " << reader.GetFilename() << ", " << header.sampleRate << " Hz, " << header.bitsPerSample << " bits, " << ((header.channels > 0x01) ? "stereo" : "mono") << std::endl; - transmitter->transmit(reader, frequency, bandwidth, dmaChannel, optind < argc); - } while (play && (optind < argc)); + transmitter->Transmit(reader, frequency, bandwidth, dmaChannel, optind < argc); + } while (!stop && (optind < argc)); } catch (std::exception &catched) { std::cout << "Error: " << catched.what() << std::endl; + finally(); return 1; } + finally(); return 0; } diff --git a/makefile b/makefile index b90b4a0..b0ac3cb 100644 --- a/makefile +++ b/makefile @@ -1,6 +1,10 @@ EXECUTABLE = fm_transmitter -VERSION = 0.9.3 +VERSION = 0.9.4 FLAGS = -Wall -O3 -std=c++11 +TRANSMITTER = -fno-strict-aliasing -I/opt/vc/include +ifeq ($(GPIO21), 1) + TRANSMITTER += -DGPIO21 +endif all: main.o mailbox.o sample.o wave_reader.o transmitter.o g++ -L/opt/vc/lib -lm -lpthread -lbcm_host -o $(EXECUTABLE) main.o mailbox.o sample.o wave_reader.o transmitter.o @@ -15,7 +19,7 @@ wave_reader.o: wave_reader.cpp wave_reader.hpp g++ $(FLAGS) -c wave_reader.cpp transmitter.o: transmitter.cpp transmitter.hpp - g++ $(FLAGS) -fno-strict-aliasing -I/opt/vc/include -c transmitter.cpp + g++ $(FLAGS) $(TRANSMITTER) -c transmitter.cpp main.o: main.cpp g++ $(FLAGS) -DVERSION=\"$(VERSION)\" -DEXECUTABLE=\"$(EXECUTABLE)\" -c main.cpp diff --git a/pcm_wave_header.hpp b/pcm_wave_header.hpp deleted file mode 100644 index 88da97c..0000000 --- a/pcm_wave_header.hpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - fm_transmitter - use Raspberry Pi as FM transmitter - - Copyright (c) 2019, Marcin Kondej - All rights reserved. - - See https://github.com/markondej/fm_transmitter - - Redistribution and use in source and binary forms, with or without modification, are - permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this list - of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, this - list of conditions and the following disclaimer in the documentation and/or other - materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of its contributors may be - used to endorse or promote products derived from this software without specific - prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY - EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT - SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR - BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY - WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#ifndef PCM_WAVE_HEADER_HPP -#define PCM_WAVE_HEADER_HPP - -#include - -#define WAVE_FORMAT_PCM 0x0001 - -struct PCMWaveHeader -{ - uint8_t chunkID[4]; - uint32_t chunkSize; - uint8_t format[4]; - uint8_t subchunk1ID[4]; - uint32_t subchunk1Size; - uint16_t audioFormat; - uint16_t channels; - uint32_t sampleRate; - uint32_t byteRate; - uint16_t blockAlign; - uint16_t bitsPerSample; - uint8_t subchunk2ID[4]; - uint32_t subchunk2Size; -}; - -#endif // PCM_WAVE_HEADER_HPP diff --git a/sample.cpp b/sample.cpp index 749d8d8..c7511e2 100644 --- a/sample.cpp +++ b/sample.cpp @@ -1,7 +1,7 @@ /* - fm_transmitter - use Raspberry Pi as FM transmitter + FM Transmitter - use Raspberry Pi as FM transmitter - Copyright (c) 2019, Marcin Kondej + Copyright (c) 2020, Marcin Kondej All rights reserved. See https://github.com/markondej/fm_transmitter @@ -34,25 +34,27 @@ #include "sample.hpp" #include -Sample::Sample(uint8_t *data, uint16_t channels, uint16_t bitsPerChannel) +Sample::Sample(uint8_t *data, unsigned channels, unsigned bitsPerChannel) : value(0.f) { - int32_t sum = 0; + int sum = 0; int16_t *channelValues = new int16_t[channels]; - int16_t multiplier = bitsPerChannel >> 3; - for (uint32_t i = 0; i < channels; i++) { - if (multiplier > 1) { - channelValues[i] = (data[(i + 1) * multiplier - 1] << 8) | data[(i + 1) * multiplier - 2]; - } else { + for (unsigned i = 0; i < channels; i++) { + switch (bitsPerChannel >> 3) { + case 2: + channelValues[i] = (data[((i + 1) << 1) - 1] << 8) | data[((i + 1) << 1) - 2]; + break; + case 1: channelValues[i] = (static_cast(data[i]) - 0x80) << 8; + break; } sum += channelValues[i]; } - value = 2 * sum / channels / static_cast(USHRT_MAX); + value = 2 * sum / (static_cast(USHRT_MAX) * channels); delete[] channelValues; } -float Sample::getMonoValue() const +float Sample::GetMonoValue() const { return value; -} \ No newline at end of file +} diff --git a/sample.hpp b/sample.hpp index ed3fc86..eccdab2 100644 --- a/sample.hpp +++ b/sample.hpp @@ -1,7 +1,7 @@ /* - fm_transmitter - use Raspberry Pi as FM transmitter + FM Transmitter - use Raspberry Pi as FM transmitter - Copyright (c) 2019, Marcin Kondej + Copyright (c) 2020, Marcin Kondej All rights reserved. See https://github.com/markondej/fm_transmitter @@ -39,8 +39,8 @@ class Sample { public: - Sample(uint8_t *data, uint16_t channels, uint16_t bitsPerChannel); - float getMonoValue() const; + Sample(uint8_t *data, unsigned channels, unsigned bitsPerChannel); + float GetMonoValue() const; protected: float value; }; diff --git a/transmitter.cpp b/transmitter.cpp index 01b34a2..59bf9db 100644 --- a/transmitter.cpp +++ b/transmitter.cpp @@ -1,7 +1,7 @@ /* - fm_transmitter - use Raspberry Pi as FM transmitter + FM Transmitter - use Raspberry Pi as FM transmitter - Copyright (c) 2019, Marcin Kondej + Copyright (c) 2020, Marcin Kondej All rights reserved. See https://github.com/markondej/fm_transmitter @@ -40,18 +40,19 @@ #include #include -#define PERIPHERALS_PHYS_BASE 0x7E000000 +#define PERIPHERALS_PHYS_BASE 0x7e000000 #define BCM2835_PERI_VIRT_BASE 0x20000000 -#define BCM2838_PERI_VIRT_BASE 0xFE000000 +#define BCM2838_PERI_VIRT_BASE 0xfe000000 #define DMA0_BASE_OFFSET 0x00007000 -#define DMA15_BASE_OFFSET 0x00E05000 +#define DMA15_BASE_OFFSET 0x00e05000 #define CLK0_BASE_OFFSET 0x00101070 -#define PWMCLK_BASE_OFFSET 0x001010A0 +#define CLK1_BASE_OFFSET 0x00101078 +#define PWMCLK_BASE_OFFSET 0x001010a0 #define GPIO_BASE_OFFSET 0x00200000 -#define PWM_BASE_OFFSET 0x0020C000 +#define PWM_BASE_OFFSET 0x0020c000 #define TIMER_BASE_OFFSET 0x00003000 -#define BCM2835_MEM_FLAG 0x0C +#define BCM2835_MEM_FLAG 0x0c #define BCM2838_MEM_FLAG 0x04 #define BCM2835_PLLD_FREQ 500 @@ -113,240 +114,294 @@ struct DMARegisters { uint32_t debug; }; -struct AllocatedMemory { - uint32_t handle, size, physicalBase, virtualBase; - int mBoxFd; +class Peripherals +{ + public: + virtual ~Peripherals() { + munmap(peripherals, GetSize()); + } + Peripherals(const Peripherals &) = delete; + Peripherals(Peripherals &&) = delete; + Peripherals &operator=(const Peripherals &) = delete; + static Peripherals &GetInstance() { + static Peripherals instance; + return instance; + } + inline uint32_t GetPhysicalAddress(volatile void *object) const { + return PERIPHERALS_PHYS_BASE + (reinterpret_cast(object) - reinterpret_cast(peripherals)); + } + inline uint32_t GetVirtualAddress(uint32_t offset) const { + return reinterpret_cast(peripherals) + offset; + } + inline static uint32_t GetVirtualBaseAddress() { + return (bcm_host_get_peripheral_size() == BCM2838_PERI_VIRT_BASE) ? BCM2838_PERI_VIRT_BASE : bcm_host_get_peripheral_address(); + } + inline static float GetClockFrequency() { + return (Peripherals::GetVirtualBaseAddress() == BCM2838_PERI_VIRT_BASE) ? BCM2838_PLLD_FREQ : BCM2835_PLLD_FREQ; + } + private: + Peripherals() { + int memFd; + if ((memFd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) { + throw std::runtime_error("Cannot open /dev/mem file (permission denied)"); + } + + peripherals = mmap(nullptr, GetSize(), PROT_READ | PROT_WRITE, MAP_SHARED, memFd, GetVirtualBaseAddress()); + close(memFd); + if (peripherals == MAP_FAILED) { + throw std::runtime_error("Cannot obtain access to peripherals (mmap error)"); + } + } + unsigned GetSize() { + unsigned size = bcm_host_get_peripheral_size(); + if (size == BCM2838_PERI_VIRT_BASE) { + size = 0x01000000; + } + return size; + } + + void *peripherals; +}; + +class AllocatedMemory +{ + public: + AllocatedMemory(unsigned size) { + mBoxFd = mbox_open(); + memSize = size; + if (memSize % PAGE_SIZE) { + memSize = (memSize / PAGE_SIZE + 1) * PAGE_SIZE; + } + memHandle = mem_alloc(mBoxFd, size, PAGE_SIZE, (Peripherals::GetVirtualBaseAddress() == BCM2835_PERI_VIRT_BASE) ? BCM2835_MEM_FLAG : BCM2838_MEM_FLAG); + if (!memHandle) { + mbox_close(mBoxFd); + memSize = 0; + throw std::runtime_error("Cannot allocate memory (" + std::to_string(size) + "bytes"); + } + memAddress = mem_lock(mBoxFd, memHandle); + memAllocated = mapmem(memAddress & ~0xc0000000, memSize); + } + virtual ~AllocatedMemory() { + unmapmem(memAllocated, memSize); + mem_unlock(mBoxFd, memHandle); + mem_free(mBoxFd, memHandle); + mbox_close(mBoxFd); + memSize = 0; + } + AllocatedMemory(const AllocatedMemory &) = delete; + AllocatedMemory(AllocatedMemory &&) = delete; + AllocatedMemory &operator=(const AllocatedMemory &) = delete; + inline uint32_t GetPhysicalAddress(volatile void *object) const { + return (memSize) ? memAddress + (reinterpret_cast(object) - reinterpret_cast(memAllocated)) : 0x00000000; + } + inline uint32_t GetAddress() const { + return reinterpret_cast(memAllocated); + } + private: + unsigned memSize, memHandle; + uint32_t memAddress; + void *memAllocated; + int mBoxFd; +}; + +class Device +{ + public: + Device() { + peripherals = &Peripherals::GetInstance(); + } + Device(const Device &) = delete; + Device(Device &&) = delete; + Device &operator=(const Device &) = delete; + protected: + Peripherals *peripherals; +}; + +class ClockDevice : public Device +{ + public: + ClockDevice(uint32_t clockAddress, unsigned divisor) { + clock = reinterpret_cast(peripherals->GetVirtualAddress(clockAddress)); + clock->ctl = (0x5a << 24) | 0x06; + std::this_thread::sleep_for(std::chrono::microseconds(1000)); + clock->div = (0x5a << 24) | (0xffffff & divisor); + clock->ctl = (0x5a << 24) | (0x01 << 9) | (0x01 << 4) | 0x06; + } + virtual ~ClockDevice() { + clock->ctl = (0x5a << 24) | 0x06; + } + protected: + volatile ClockRegisters *clock; +}; + +class ClockOutput : public ClockDevice +{ + public: +#ifndef GPIO21 + ClockOutput(unsigned divisor) : ClockDevice(CLK0_BASE_OFFSET, divisor) { + output = reinterpret_cast(peripherals->GetVirtualAddress(GPIO_BASE_OFFSET)); + *output = (*output & 0xffff8fff) | (0x04 << 12); +#else + ClockOutput(unsigned divisor) : ClockDevice(CLK1_BASE_OFFSET, divisor) { + output = reinterpret_cast(peripherals->GetVirtualAddress(GPIO_BASE_OFFSET + 0x08)); + *output = (*output & 0xffffffc7) | (0x02 << 3); +#endif + } + virtual ~ClockOutput() { +#ifndef GPIO21 + *output = (*output & 0xffff8fff) | (0x01 << 12); +#else + *output = (*output & 0xffffffc7) | (0x02 << 3); +#endif + } + inline void SetDivisor(unsigned divisor) { + clock->div = (0x5a << 24) | (0xffffff & divisor); + } + inline volatile uint32_t &GetDivisor() { + return clock->div; + } + private: + volatile uint32_t *output; +}; + +class PWMController : public ClockDevice +{ + public: + PWMController(unsigned sampleRate) : ClockDevice(PWMCLK_BASE_OFFSET, static_cast(Peripherals::GetClockFrequency() * 1000000.f * (0x01 << 12) / (PWM_WRITES_PER_SAMPLE * PWM_CHANNEL_RANGE * sampleRate))) { + pwm = reinterpret_cast(peripherals->GetVirtualAddress(PWM_BASE_OFFSET)); + pwm->ctl = 0x00000000; + std::this_thread::sleep_for(std::chrono::microseconds(1000)); + pwm->status = 0x01fc; + pwm->ctl = (0x01 << 6); + std::this_thread::sleep_for(std::chrono::microseconds(1000)); + pwm->chn1Range = PWM_CHANNEL_RANGE; + pwm->dmaConf = (0x01 << 31) | 0x0707; + pwm->ctl = (0x01 << 5) | (0x01 << 2) | 0x01; + } + virtual ~PWMController() { + pwm->ctl = 0x00000000; + } + inline volatile uint32_t &GetFifoIn() { + return pwm->fifoIn; + } + private: + volatile PWMRegisters *pwm; +}; + +class DMAController : public Device +{ + public: + DMAController(uint32_t controllBlockAddress, unsigned dmaChannel) { + dma = reinterpret_cast(peripherals->GetVirtualAddress((dmaChannel < 15) ? DMA0_BASE_OFFSET + dmaChannel * 0x100 : DMA15_BASE_OFFSET)); + dma->ctlStatus = (0x01 << 31); + std::this_thread::sleep_for(std::chrono::microseconds(1000)); + dma->ctlStatus = (0x01 << 2) | (0x01 << 1); + dma->cbAddress = controllBlockAddress; + dma->ctlStatus = (0xff << 16) | 0x01; + } + virtual ~DMAController() { + dma->ctlStatus = (0x01 << 31); + } + inline void SetControllBlockAddress(uint32_t address) { + dma->cbAddress = address; + } + inline volatile uint32_t &GetControllBlockAddress() { + return dma->cbAddress; + } + private: + volatile DMARegisters *dma; }; bool Transmitter::transmitting = false; -volatile ClockRegisters *Transmitter::output = nullptr; -uint32_t Transmitter::sampleOffset, Transmitter::clockDivisor, Transmitter::divisorRange, Transmitter::sampleRate; -std::vector Transmitter::samples; -std::mutex Transmitter::samplesMutex; -void *Transmitter::peripherals; Transmitter::Transmitter() + : output(nullptr), stopped(true) { - int memFd; - if ((memFd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) { - throw std::runtime_error("Cannot open /dev/mem (permission denied)"); - } +} - peripherals = mmap(nullptr, getPeripheralsSize(), PROT_READ | PROT_WRITE, MAP_SHARED, memFd, getPeripheralsVirtBaseAddress()); - close(memFd); - if (peripherals == MAP_FAILED) { - throw std::runtime_error("Cannot obtain access to peripherals (mmap error)"); +Transmitter::~Transmitter() { + if (output != nullptr) { + delete output; } } -Transmitter::~Transmitter() -{ - munmap(peripherals, getPeripheralsSize()); -} - -Transmitter &Transmitter::getInstance() -{ - static Transmitter instance; - return instance; -} - -uint32_t Transmitter::getPeripheralsVirtBaseAddress() const -{ - return (bcm_host_get_peripheral_size() == BCM2838_PERI_VIRT_BASE) ? BCM2838_PERI_VIRT_BASE : bcm_host_get_peripheral_address(); -} - -uint32_t Transmitter::getPeripheralsSize() const -{ - uint32_t size = bcm_host_get_peripheral_size(); - if (size == BCM2838_PERI_VIRT_BASE) { - size = 0x01000000; - } - return size; -} - -float Transmitter::getSourceFreq() const -{ - return (getPeripheralsVirtBaseAddress() == BCM2838_PERI_VIRT_BASE) ? BCM2838_PLLD_FREQ : BCM2835_PLLD_FREQ; -} - -uint32_t Transmitter::getPeripheralPhysAddress(volatile void *object) const -{ - return PERIPHERALS_PHYS_BASE + (reinterpret_cast(object) - reinterpret_cast(peripherals)); -} - -uint32_t Transmitter::getPeripheralVirtAddress(uint32_t offset) -{ - return reinterpret_cast(peripherals) + offset; -} - -uint32_t Transmitter::getMemoryPhysAddress(AllocatedMemory &memory, volatile void *object) const -{ - return memory.physicalBase + (reinterpret_cast(object) - memory.virtualBase); -} - -AllocatedMemory Transmitter::allocateMemory(uint32_t size) -{ - AllocatedMemory memory; - memory.size = 0x00000000; - memory.mBoxFd = mbox_open(); - if (size % PAGE_SIZE) { - size = (size / PAGE_SIZE + 1) * PAGE_SIZE; - } - memory.handle = mem_alloc(memory.mBoxFd, size, PAGE_SIZE, (getPeripheralsVirtBaseAddress() == BCM2835_PERI_VIRT_BASE) ? BCM2835_MEM_FLAG : BCM2838_MEM_FLAG); - if (!memory.handle) { - mbox_close(memory.mBoxFd); - return memory; - } - memory.physicalBase = mem_lock(memory.mBoxFd, memory.handle); - memory.virtualBase = reinterpret_cast(mapmem(memory.physicalBase & ~0xC0000000, size)); - memory.size = size; - return memory; -} - -void Transmitter::freeMemory(AllocatedMemory &memory) -{ - unmapmem(reinterpret_cast(memory.virtualBase), memory.size); - mem_unlock(memory.mBoxFd, memory.handle); - mem_free(memory.mBoxFd, memory.handle); - mbox_close(memory.mBoxFd); - memory.size = 0x00000000; -} - -volatile PWMRegisters *Transmitter::initPwmController() -{ - volatile ClockRegisters *pwmClk = reinterpret_cast(getPeripheralVirtAddress(PWMCLK_BASE_OFFSET)); - float pwmClkFreq = PWM_WRITES_PER_SAMPLE * PWM_CHANNEL_RANGE * sampleRate / 1000000; - pwmClk->ctl = (0x5A << 24) | 0x06; - std::this_thread::sleep_for(std::chrono::microseconds(1000)); - pwmClk->div = (0x5A << 24) | static_cast(getSourceFreq() * (0x01 << 12) / pwmClkFreq); - pwmClk->ctl = (0x5A << 24) | (0x01 << 4) | 0x06; - - volatile PWMRegisters *pwm = reinterpret_cast(getPeripheralVirtAddress(PWM_BASE_OFFSET)); - pwm->ctl = 0x00000000; - std::this_thread::sleep_for(std::chrono::microseconds(1000)); - pwm->status = 0x01FC; - pwm->ctl = (0x01 << 6); - std::this_thread::sleep_for(std::chrono::microseconds(1000)); - pwm->chn1Range = PWM_CHANNEL_RANGE; - pwm->dmaConf = (0x01 << 31) | 0x0707; - pwm->ctl = (0x01 << 5) | (0x01 << 2) | 0x01; - return pwm; -} - -void Transmitter::closePwmController(volatile PWMRegisters *pwm) -{ - pwm->ctl = 0x00000000; -} - -volatile DMARegisters *Transmitter::startDma(AllocatedMemory &memory, volatile DMAControllBlock *dmaCb, uint8_t dmaChannel) -{ - volatile DMARegisters *dma = reinterpret_cast(getPeripheralVirtAddress((dmaChannel < 15) ? DMA0_BASE_OFFSET + dmaChannel * 0x100 : DMA15_BASE_OFFSET)); - dma->ctlStatus = (0x01 << 31); - std::this_thread::sleep_for(std::chrono::microseconds(1000)); - dma->ctlStatus = (0x01 << 2) | (0x01 << 1); - dma->cbAddress = getMemoryPhysAddress(memory, dmaCb); - dma->ctlStatus = (0xFF << 16) | 0x01; - return dma; -} - -void Transmitter::closeDma(volatile DMARegisters *dma) -{ - dma->ctlStatus = (0x01 << 31); -} - -volatile ClockRegisters *Transmitter::initClockOutput() -{ - volatile ClockRegisters *clock = reinterpret_cast(getPeripheralVirtAddress(CLK0_BASE_OFFSET)); - volatile uint32_t *gpio = reinterpret_cast(getPeripheralVirtAddress(GPIO_BASE_OFFSET)); - clock->ctl = (0x5A << 24) | 0x06; - std::this_thread::sleep_for(std::chrono::microseconds(1000)); - clock->div = (0x5A << 24) | clockDivisor; - clock->ctl = (0x5A << 24) | (0x01 << 9) | (0x01 << 4) | 0x06; - *gpio = (*gpio & 0xFFFF8FFF) | (0x01 << 14); - return clock; -} - -void Transmitter::closeClockOutput(volatile ClockRegisters *clock) -{ - clock->ctl = (0x5A << 24) | 0x06; -} - -void Transmitter::transmit(WaveReader &reader, float frequency, float bandwidth, uint8_t dmaChannel, bool preserveCarrierOnExit) +void Transmitter::Transmit(WaveReader &reader, float frequency, float bandwidth, unsigned dmaChannel, bool preserveCarrier) { if (transmitting) { - throw std::runtime_error("Cannot play, transmitter already in use"); + throw std::runtime_error("Cannot transmit, transmitter already in use"); } - transmitting = true; + stopped = false; - PCMWaveHeader header = reader.getHeader(); - uint32_t bufferSize = static_cast(static_cast(header.sampleRate) * BUFFER_TIME / 1000000); + WaveHeader header = reader.GetHeader(); + unsigned bufferSize = static_cast(static_cast(header.sampleRate) * BUFFER_TIME / 1000000); - preserveCarrier = preserveCarrierOnExit; - clockDivisor = static_cast(round(getSourceFreq() * (0x01 << 12) / frequency)); - divisorRange = clockDivisor - static_cast(round(getSourceFreq() * (0x01 << 12) / (frequency + 0.0005f * bandwidth))); - sampleRate = header.sampleRate; + unsigned clockDivisor = static_cast(round(Peripherals::GetClockFrequency() * (0x01 << 12) / frequency)); + unsigned divisorRange = clockDivisor - static_cast(round(Peripherals::GetClockFrequency() * (0x01 << 12) / (frequency + 0.0005f * bandwidth))); if (output == nullptr) { - output = initClockOutput(); + output = new ClockOutput(clockDivisor); } auto finally = [&]() { if (!preserveCarrier) { - closeClockOutput(output); + delete output; output = nullptr; } + transmitting = false; }; try { - if (dmaChannel != 0xFF) { - transmitViaDma(reader, bufferSize, dmaChannel); + if (dmaChannel != 0xff) { + TransmitViaDma(reader, *output, header.sampleRate, bufferSize, clockDivisor, divisorRange, dmaChannel); } else { - transmitViaCpu(reader, bufferSize); + TransmitViaCpu(reader, *output, header.sampleRate, bufferSize, clockDivisor, divisorRange); } } catch (...) { - preserveCarrier = false; finally(); throw; } finally(); } -void Transmitter::transmitViaCpu(WaveReader &reader, uint32_t bufferSize) +void Transmitter::Stop() { - samples = reader.getSamples(bufferSize, transmitting); - if (!samples.size()) { + stopped = true; +} + +void Transmitter::TransmitViaCpu(WaveReader &reader, ClockOutput &output, unsigned sampleRate, unsigned bufferSize, unsigned clockDivisor, unsigned divisorRange) +{ + std::vector samples = reader.GetSamples(bufferSize, stopped); + if (samples.empty()) { return; } - sampleOffset = 0; + unsigned sampleOffset = 0; bool eof = samples.size() < bufferSize; - std::thread txThread(Transmitter::transmitThread); + std::thread transmitterThread(Transmitter::TransmitterThread, this, &output, sampleRate, clockDivisor, divisorRange, &sampleOffset, &samples); std::this_thread::sleep_for(std::chrono::microseconds(BUFFER_TIME / 2)); - bool wait = false; auto finally = [&]() { - transmitting = false; - txThread.join(); + stopped = true; + transmitterThread.join(); samples.clear(); }; try { - while (!eof && transmitting) { - if (wait) { - std::this_thread::sleep_for(std::chrono::microseconds(BUFFER_TIME / 2)); - } - std::lock_guard locked(samplesMutex); - if (!samples.size()) { - if (!reader.setSampleOffset(sampleOffset + bufferSize)) { - break; + while (!eof && !stopped) { + { + std::lock_guard lock(access); + if (samples.empty()) { + if (!reader.SetSampleOffset(sampleOffset + bufferSize)) { + break; + } + samples = reader.GetSamples(bufferSize, stopped); + if (samples.empty()) { + break; + } + eof = samples.size() < bufferSize; } - samples = reader.getSamples(bufferSize, transmitting); - if (!samples.size()) { - break; - } - eof = samples.size() < bufferSize; } - wait = true; + std::this_thread::sleep_for(std::chrono::microseconds(BUFFER_TIME / 2)); } } catch (...) { finally(); @@ -355,86 +410,80 @@ void Transmitter::transmitViaCpu(WaveReader &reader, uint32_t bufferSize) finally(); } -void Transmitter::transmitViaDma(WaveReader &reader, uint32_t bufferSize, uint8_t dmaChannel) +void Transmitter::TransmitViaDma(WaveReader &reader, ClockOutput &output, unsigned sampleRate, unsigned bufferSize, unsigned clockDivisor, unsigned divisorRange, unsigned dmaChannel) { if (dmaChannel > 15) { throw std::runtime_error("DMA channel number out of range (0 - 15)"); } - samples = reader.getSamples(bufferSize, transmitting); - if (!samples.size()) { + AllocatedMemory allocated(sizeof(uint32_t) * (bufferSize) + sizeof(DMAControllBlock) * (2 * bufferSize) + sizeof(uint32_t)); + + std::vector samples = reader.GetSamples(bufferSize, stopped); + if (samples.empty()) { return; } + bool eof = false; if (samples.size() < bufferSize) { bufferSize = samples.size(); eof = true; } - AllocatedMemory dmaMemory = allocateMemory(sizeof(uint32_t) * (bufferSize + 1) + sizeof(DMAControllBlock) * (2 * bufferSize)); - if (!dmaMemory.size) { - throw std::runtime_error("Cannot allocate memory"); - } + PWMController pwm(sampleRate); + Peripherals &peripherals = Peripherals::GetInstance(); - volatile PWMRegisters *pwm = initPwmController(); + unsigned i, cbOffset = 0; - uint32_t i, cbOffset = 0; - - volatile DMAControllBlock *dmaCb = reinterpret_cast(dmaMemory.virtualBase); + volatile DMAControllBlock *dmaCb = reinterpret_cast(allocated.GetAddress()); volatile uint32_t *clkDiv = reinterpret_cast(reinterpret_cast(dmaCb) + 2 * sizeof(DMAControllBlock) * bufferSize); volatile uint32_t *pwmFifoData = reinterpret_cast(reinterpret_cast(clkDiv) + sizeof(uint32_t) * bufferSize); for (i = 0; i < bufferSize; i++) { - float value = samples[i].getMonoValue(); - clkDiv[i] = (0x5A << 24) | (clockDivisor - static_cast(round(value * divisorRange))); + float value = samples[i].GetMonoValue(); + clkDiv[i] = (0x5a << 24) | (0xffffff & (clockDivisor - static_cast(round(value * divisorRange)))); dmaCb[cbOffset].transferInfo = (0x01 << 26) | (0x01 << 3); - dmaCb[cbOffset].srcAddress = getMemoryPhysAddress(dmaMemory, &clkDiv[i]); - dmaCb[cbOffset].dstAddress = getPeripheralPhysAddress(&output->div); + dmaCb[cbOffset].srcAddress = allocated.GetPhysicalAddress(&clkDiv[i]); + dmaCb[cbOffset].dstAddress = peripherals.GetPhysicalAddress(&output.GetDivisor()); dmaCb[cbOffset].transferLen = sizeof(uint32_t); dmaCb[cbOffset].stride = 0; - dmaCb[cbOffset].nextCbAddress = getMemoryPhysAddress(dmaMemory, &dmaCb[cbOffset + 1]); + dmaCb[cbOffset].nextCbAddress = allocated.GetPhysicalAddress(&dmaCb[cbOffset + 1]); cbOffset++; dmaCb[cbOffset].transferInfo = (0x01 << 26) | (0x05 << 16) | (0x01 << 6) | (0x01 << 3); - dmaCb[cbOffset].srcAddress = getMemoryPhysAddress(dmaMemory, pwmFifoData); - dmaCb[cbOffset].dstAddress = getPeripheralPhysAddress(&pwm->fifoIn); + dmaCb[cbOffset].srcAddress = allocated.GetPhysicalAddress(pwmFifoData); + dmaCb[cbOffset].dstAddress = peripherals.GetPhysicalAddress(&pwm.GetFifoIn()); dmaCb[cbOffset].transferLen = sizeof(uint32_t) * PWM_WRITES_PER_SAMPLE; dmaCb[cbOffset].stride = 0; - dmaCb[cbOffset].nextCbAddress = getMemoryPhysAddress(dmaMemory, (i < bufferSize - 1) ? &dmaCb[cbOffset + 1] : dmaCb); + dmaCb[cbOffset].nextCbAddress = allocated.GetPhysicalAddress((i < bufferSize - 1) ? &dmaCb[cbOffset + 1] : dmaCb); cbOffset++; } *pwmFifoData = 0x00000000; - volatile DMARegisters *dma = startDma(dmaMemory, dmaCb, dmaChannel); + DMAController dma(allocated.GetPhysicalAddress(dmaCb), dmaChannel); std::this_thread::sleep_for(std::chrono::microseconds(BUFFER_TIME / 4)); auto finally = [&]() { dmaCb[(cbOffset < 2 * bufferSize) ? cbOffset : 0].nextCbAddress = 0x00000000; - while (dma->cbAddress != 0x00000000) { + while (dma.GetControllBlockAddress() != 0x00000000) { std::this_thread::sleep_for(std::chrono::microseconds(1000)); } - - closeDma(dma); - closePwmController(pwm); - - freeMemory(dmaMemory); - transmitting = false; + stopped = true; samples.clear(); }; try { - while (!eof && transmitting) { - samples = reader.getSamples(bufferSize, transmitting); + while (!eof && !stopped) { + samples = reader.GetSamples(bufferSize, stopped); if (!samples.size()) { break; } cbOffset = 0; eof = samples.size() < bufferSize; for (i = 0; i < samples.size(); i++) { - float value = samples[i].getMonoValue(); - while (i == ((dma->cbAddress - getMemoryPhysAddress(dmaMemory, dmaCb)) / (2 * sizeof(DMAControllBlock)))) { + float value = samples[i].GetMonoValue(); + while (i == ((dma.GetControllBlockAddress() - allocated.GetPhysicalAddress(dmaCb)) / (2 * sizeof(DMAControllBlock)))) { std::this_thread::sleep_for(std::chrono::microseconds(1000)); } - clkDiv[i] = (0x5A << 24) | (clockDivisor - static_cast(round(value * divisorRange))); + clkDiv[i] = (0x5a << 24) | (0xffffff & (clockDivisor - static_cast(round(value * divisorRange)))); cbOffset += 2; } } @@ -445,44 +494,42 @@ void Transmitter::transmitViaDma(WaveReader &reader, uint32_t bufferSize, uint8_ finally(); } -void Transmitter::transmitThread() +void Transmitter::TransmitterThread(Transmitter *instance, ClockOutput *output, unsigned sampleRate, unsigned clockDivisor, unsigned divisorRange, unsigned *sampleOffset, std::vector *samples) { - volatile TimerRegisters *timer = reinterpret_cast(getPeripheralVirtAddress(TIMER_BASE_OFFSET)); + Peripherals &peripherals = Peripherals::GetInstance(); + + volatile TimerRegisters *timer = reinterpret_cast(peripherals.GetVirtualAddress(TIMER_BASE_OFFSET)); uint64_t current = *(reinterpret_cast(&timer->low)); uint64_t playbackStart = current; - while (transmitting) { - uint64_t start = current; - - bool locked = samplesMutex.try_lock(); - auto unlock = [&]() { - if (locked) { - samplesMutex.unlock(); + while (true) { + std::vector loadedSamples; + while (true) { + { + std::lock_guard lock(instance->access); + if (instance->stopped) { + return; + } + loadedSamples = std::move(*samples); + current = *(reinterpret_cast(&timer->low)); + if (!loadedSamples.empty()) { + *sampleOffset = (current - playbackStart) * sampleRate / 1000000; + break; + } } - }; - while ((!locked || !samples.size()) && transmitting) { - unlock(); std::this_thread::sleep_for(std::chrono::microseconds(1)); - current = *(reinterpret_cast(&timer->low)); - locked = samplesMutex.try_lock(); - } - if (!transmitting) { - unlock(); - break; - } - std::vector loaded = std::move(samples); - unlock(); + }; - sampleOffset = (current - playbackStart) * sampleRate / 1000000; - uint32_t offset = (current - start) * sampleRate / 1000000; + uint64_t start = current; + unsigned offset = (current - start) * sampleRate / 1000000; while (true) { - if (offset >= loaded.size()) { + if (offset >= loadedSamples.size()) { break; } - uint32_t prevOffset = offset; - float value = loaded[offset].getMonoValue(); - output->div = (0x5A << 24) | (clockDivisor - static_cast(round(value * divisorRange))); + unsigned prevOffset = offset; + float value = loadedSamples[offset].GetMonoValue(); + instance->output->SetDivisor(clockDivisor - static_cast(round(value * divisorRange))); while (offset == prevOffset) { std::this_thread::sleep_for(std::chrono::microseconds(1)); // asm("nop"); current = *(reinterpret_cast(&timer->low));; @@ -491,9 +538,3 @@ void Transmitter::transmitThread() } } } - -void Transmitter::stop() -{ - preserveCarrier = false; - transmitting = false; -} diff --git a/transmitter.hpp b/transmitter.hpp index a658c64..a18ffbb 100644 --- a/transmitter.hpp +++ b/transmitter.hpp @@ -1,7 +1,7 @@ /* - fm_transmitter - use Raspberry Pi as FM transmitter + FM Transmitter - use Raspberry Pi as FM transmitter - Copyright (c) 2019, Marcin Kondej + Copyright (c) 2020, Marcin Kondej All rights reserved. See https://github.com/markondej/fm_transmitter @@ -37,50 +37,27 @@ #include "wave_reader.hpp" #include -struct AllocatedMemory; -struct PWMRegisters; -struct DMARegisters; -struct DMAControllBlock; -struct ClockRegisters; +class ClockOutput; class Transmitter { public: - ~Transmitter(); + Transmitter(); + virtual ~Transmitter(); Transmitter(const Transmitter &) = delete; Transmitter(Transmitter &&) = delete; Transmitter &operator=(const Transmitter &) = delete; - static Transmitter &getInstance(); - void transmit(WaveReader &reader, float frequency, float bandwidth, uint8_t dmaChannel, bool preserveCarrierOnExit); - void stop(); + void Transmit(WaveReader &reader, float frequency, float bandwidth, unsigned dmaChannel, bool preserveCarrier); + void Stop(); private: - Transmitter(); - uint32_t getPeripheralsVirtBaseAddress() const; - uint32_t getPeripheralsSize() const; - float getSourceFreq() const; - uint32_t getPeripheralPhysAddress(volatile void *object) const; - static uint32_t getPeripheralVirtAddress(uint32_t offset); - uint32_t getMemoryPhysAddress(AllocatedMemory &memory, volatile void *object) const; - AllocatedMemory allocateMemory(uint32_t size); - void freeMemory(AllocatedMemory &memory); - volatile PWMRegisters *initPwmController(); - void closePwmController(volatile PWMRegisters *pwm); - volatile DMARegisters *startDma(AllocatedMemory &memory, volatile DMAControllBlock *dmaCb, uint8_t dmaChannel); - void closeDma(volatile DMARegisters *dma); - volatile ClockRegisters *initClockOutput(); - void closeClockOutput(volatile ClockRegisters *clock); - void transmitViaCpu(WaveReader &reader, uint32_t bufferSize); - void transmitViaDma(WaveReader &reader, uint32_t bufferSize, uint8_t dmaChannel); - static void transmitThread(); + void TransmitViaCpu(WaveReader &reader, ClockOutput &output, unsigned sampleRate, unsigned bufferSize, unsigned clockDivisor, unsigned divisorRange); + void TransmitViaDma(WaveReader &reader, ClockOutput &output, unsigned sampleRate, unsigned bufferSize, unsigned clockDivisor, unsigned divisorRange, unsigned dmaChannel); + static void TransmitterThread(Transmitter *instance, ClockOutput *output, unsigned sampleRate, unsigned clockDivisor, unsigned divisorRange, unsigned *sampleOffset, std::vector *samples); - bool preserveCarrier; - - static void *peripherals; static bool transmitting; - static uint32_t sampleOffset, clockDivisor, divisorRange, sampleRate; - static volatile ClockRegisters *output; - static std::vector samples; - static std::mutex samplesMutex; + ClockOutput *output; + std::mutex access; + bool stopped; }; #endif // TRANSMITTER_HPP diff --git a/wave_reader.cpp b/wave_reader.cpp index 39eef87..b77ea68 100644 --- a/wave_reader.cpp +++ b/wave_reader.cpp @@ -1,7 +1,7 @@ /* - fm_transmitter - use Raspberry Pi as FM transmitter + FM Transmitter - use Raspberry Pi as FM transmitter - Copyright (c) 2019, Marcin Kondej + Copyright (c) 2020, Marcin Kondej All rights reserved. See https://github.com/markondej/fm_transmitter @@ -39,7 +39,7 @@ #include #include -WaveReader::WaveReader(const std::string &filename, bool &continueFlag) : +WaveReader::WaveReader(const std::string &filename, bool &stop) : filename(filename), headerOffset(0), currentDataOffset(0) { if (!filename.empty()) { @@ -50,34 +50,34 @@ WaveReader::WaveReader(const std::string &filename, bool &continueFlag) : } if (fileDescriptor == -1) { - throw std::runtime_error(std::string("Cannot open ") + getFilename() + std::string(", file does not exist")); + throw std::runtime_error(std::string("Cannot open ") + GetFilename() + std::string(", file does not exist")); } try { - readData(sizeof(PCMWaveHeader::chunkID) + sizeof(PCMWaveHeader::chunkSize) + sizeof(PCMWaveHeader::format), true, continueFlag); + ReadData(sizeof(WaveHeader::chunkID) + sizeof(WaveHeader::chunkSize) + sizeof(WaveHeader::format), true, stop); if ((std::string(reinterpret_cast(header.chunkID), 4) != std::string("RIFF")) || (std::string(reinterpret_cast(header.format), 4) != std::string("WAVE"))) { - throw std::runtime_error(std::string("Error while opening ") + getFilename() + std::string(", WAVE file expected")); + throw std::runtime_error(std::string("Error while opening ") + GetFilename() + std::string(", WAVE file expected")); } - readData(sizeof(PCMWaveHeader::subchunk1ID) + sizeof(PCMWaveHeader::subchunk1Size), true, continueFlag); - uint32_t subchunk1MinSize = sizeof(PCMWaveHeader::audioFormat) + sizeof(PCMWaveHeader::channels) + - sizeof(PCMWaveHeader::sampleRate) + sizeof(PCMWaveHeader::byteRate) + sizeof(PCMWaveHeader::blockAlign) + - sizeof(PCMWaveHeader::bitsPerSample); + ReadData(sizeof(WaveHeader::subchunk1ID) + sizeof(WaveHeader::subchunk1Size), true, stop); + unsigned subchunk1MinSize = sizeof(WaveHeader::audioFormat) + sizeof(WaveHeader::channels) + + sizeof(WaveHeader::sampleRate) + sizeof(WaveHeader::byteRate) + sizeof(WaveHeader::blockAlign) + + sizeof(WaveHeader::bitsPerSample); if ((std::string(reinterpret_cast(header.subchunk1ID), 4) != std::string("fmt ")) || (header.subchunk1Size < subchunk1MinSize)) { - throw std::runtime_error(std::string("Error while opening ") + getFilename() + std::string(", data corrupted")); + throw std::runtime_error(std::string("Error while opening ") + GetFilename() + std::string(", data corrupted")); } - readData(header.subchunk1Size, true, continueFlag); + ReadData(header.subchunk1Size, true, stop); if ((header.audioFormat != WAVE_FORMAT_PCM) || (header.byteRate != (header.bitsPerSample >> 3) * header.channels * header.sampleRate) || (header.blockAlign != (header.bitsPerSample >> 3) * header.channels) || (((header.bitsPerSample >> 3) != 1) && ((header.bitsPerSample >> 3) != 2))) { - throw std::runtime_error(std::string("Error while opening ") + getFilename() + std::string(", unsupported WAVE format")); + throw std::runtime_error(std::string("Error while opening ") + GetFilename() + std::string(", unsupported WAVE format")); } - readData(sizeof(PCMWaveHeader::subchunk2ID) + sizeof(PCMWaveHeader::subchunk2Size), true, continueFlag); + ReadData(sizeof(WaveHeader::subchunk2ID) + sizeof(WaveHeader::subchunk2Size), true, stop); if (std::string(reinterpret_cast(header.subchunk2ID), 4) != std::string("data")) { - throw std::runtime_error(std::string("Error while opening ") + getFilename() + std::string(", data corrupted")); + throw std::runtime_error(std::string("Error while opening ") + GetFilename() + std::string(", data corrupted")); } } catch (...) { if (fileDescriptor != STDIN_FILENO) { @@ -98,65 +98,38 @@ WaveReader::~WaveReader() } } -std::vector WaveReader::readData(uint32_t bytesToRead, bool headerBytes, bool &continueFlag) +std::string WaveReader::GetFilename() const { - uint32_t bytesRead = 0; - std::vector data; - data.resize(bytesToRead); - while ((bytesRead < bytesToRead) && continueFlag) { - int bytes = read(fileDescriptor, &data[bytesRead], bytesToRead - bytesRead); - if (((bytes == -1) && ((fileDescriptor != STDIN_FILENO) || (errno != EAGAIN))) || - ((static_cast(bytes) < bytesToRead) && headerBytes && (fileDescriptor != STDIN_FILENO))) { - throw std::runtime_error(std::string("Error while opening ") + getFilename() + std::string(", data corrupted")); - } - if (bytes > 0) { - bytesRead += bytes; - } - if (bytesRead < bytesToRead) { - if (fileDescriptor != STDIN_FILENO) { - data.resize(bytes); - break; - } else { - std::this_thread::sleep_for(std::chrono::microseconds(1)); - } - } - } - - if (headerBytes) { - if (!continueFlag) { - throw std::runtime_error("Cannot obtain header, program interrupted"); - } - std::memcpy(&(reinterpret_cast(&header))[headerOffset], data.data(), bytesRead); - headerOffset += bytesRead; - } else { - currentDataOffset += bytesRead; - } - - return data; + return fileDescriptor != STDIN_FILENO ? filename : "STDIN"; } -std::vector WaveReader::getSamples(uint32_t quantity, bool &continueFlag) { - uint32_t bytesPerSample = (header.bitsPerSample >> 3) * header.channels; - uint32_t bytesToRead = quantity * bytesPerSample; - uint32_t bytesLeft = header.subchunk2Size - currentDataOffset; +const WaveHeader &WaveReader::GetHeader() const +{ + return header; +} + +std::vector WaveReader::GetSamples(unsigned quantity, bool &stop) { + unsigned bytesPerSample = (header.bitsPerSample >> 3) * header.channels; + unsigned bytesToRead = quantity * bytesPerSample; + unsigned bytesLeft = header.subchunk2Size - currentDataOffset; if (bytesToRead > bytesLeft) { bytesToRead = bytesLeft - bytesLeft % bytesPerSample; quantity = bytesToRead / bytesPerSample; } - std::vector data = std::move(readData(bytesToRead, false, continueFlag)); + std::vector data = std::move(ReadData(bytesToRead, false, stop)); if (data.size() < bytesToRead) { quantity = data.size() / bytesPerSample; } std::vector samples; - for (uint32_t i = 0; i < quantity; i++) { + for (unsigned i = 0; i < quantity; i++) { samples.push_back(Sample(&data[bytesPerSample * i], header.channels, header.bitsPerSample)); } return samples; } -bool WaveReader::setSampleOffset(uint32_t offset) { +bool WaveReader::SetSampleOffset(unsigned offset) { if (fileDescriptor != STDIN_FILENO) { currentDataOffset = offset * (header.bitsPerSample >> 3) * header.channels; if (lseek(fileDescriptor, dataOffset + currentDataOffset, SEEK_SET) == -1) { @@ -166,12 +139,42 @@ bool WaveReader::setSampleOffset(uint32_t offset) { return true; } -std::string WaveReader::getFilename() const +std::vector WaveReader::ReadData(unsigned bytesToRead, bool headerBytes, bool &stop) { - return fileDescriptor != STDIN_FILENO ? filename : "STDIN"; -} + unsigned bytesRead = 0; + std::vector data; + data.resize(bytesToRead); + while ((bytesRead < bytesToRead) && !stop) { + int bytes = read(fileDescriptor, &data[bytesRead], bytesToRead - bytesRead); + if (((bytes == -1) && ((fileDescriptor != STDIN_FILENO) || (errno != EAGAIN))) || + ((static_cast(bytes) < bytesToRead) && headerBytes && (fileDescriptor != STDIN_FILENO))) { + throw std::runtime_error(std::string("Error while opening ") + GetFilename() + std::string(", data corrupted")); + } + if (bytes > 0) { + bytesRead += bytes; + } + if (bytesRead < bytesToRead) { + if (fileDescriptor != STDIN_FILENO) { + data.resize(bytesRead); + break; + } else { + std::this_thread::sleep_for(std::chrono::microseconds(1)); + } + } + } -PCMWaveHeader WaveReader::getHeader() const -{ - return header; + if (headerBytes) { + if (stop) { + throw std::runtime_error("Cannot obtain header, program interrupted"); + } + std::memcpy(&(reinterpret_cast(&header))[headerOffset], data.data(), bytesRead); + headerOffset += bytesRead; + } else { + if (stop) { + data.resize(bytesRead); + } + currentDataOffset += bytesRead; + } + + return data; } diff --git a/wave_reader.hpp b/wave_reader.hpp index 0bb4f13..647c2aa 100644 --- a/wave_reader.hpp +++ b/wave_reader.hpp @@ -1,7 +1,7 @@ /* - fm_transmitter - use Raspberry Pi as FM transmitter + FM Transmitter - use Raspberry Pi as FM transmitter - Copyright (c) 2019, Marcin Kondej + Copyright (c) 2020, Marcin Kondej All rights reserved. See https://github.com/markondej/fm_transmitter @@ -34,29 +34,47 @@ #ifndef WAVE_READER_HPP #define WAVE_READER_HPP -#include "pcm_wave_header.hpp" #include "sample.hpp" #include #include +#define WAVE_FORMAT_PCM 0x0001 + +struct WaveHeader +{ + uint8_t chunkID[4]; + uint32_t chunkSize; + uint8_t format[4]; + uint8_t subchunk1ID[4]; + uint32_t subchunk1Size; + uint16_t audioFormat; + uint16_t channels; + uint32_t sampleRate; + uint32_t byteRate; + uint16_t blockAlign; + uint16_t bitsPerSample; + uint8_t subchunk2ID[4]; + uint32_t subchunk2Size; +}; + class WaveReader { public: - WaveReader(const std::string &filename, bool &continueFlag); + WaveReader(const std::string &filename, bool &stop); virtual ~WaveReader(); WaveReader(const WaveReader &) = delete; WaveReader(WaveReader &&) = delete; WaveReader &operator=(const WaveReader &) = delete; - std::string getFilename() const; - PCMWaveHeader getHeader() const; - std::vector getSamples(uint32_t quantity, bool &continueFlag); - bool setSampleOffset(uint32_t offset); + std::string GetFilename() const; + const WaveHeader &GetHeader() const; + std::vector GetSamples(unsigned quantity, bool &stop); + bool SetSampleOffset(unsigned offset); private: - std::vector readData(uint32_t bytesToRead, bool headerBytes, bool &continueFlag); + std::vector ReadData(unsigned bytesToRead, bool headerBytes, bool &stop); std::string filename; - PCMWaveHeader header; - uint32_t dataOffset, headerOffset, currentDataOffset; + WaveHeader header; + unsigned dataOffset, headerOffset, currentDataOffset; int fileDescriptor; };