kopia lustrzana https://github.com/markondej/fm_transmitter
commit
24d1a85f69
13
README.md
13
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:
|
||||
```
|
||||
|
|
38
main.cpp
38
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 <iostream>
|
||||
#include <unistd.h>
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
8
makefile
8
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
|
||||
|
|
|
@ -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 <cstdint>
|
||||
|
||||
#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
|
26
sample.cpp
26
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 <climits>
|
||||
|
||||
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<int16_t>(data[i]) - 0x80) << 8;
|
||||
break;
|
||||
}
|
||||
sum += channelValues[i];
|
||||
}
|
||||
value = 2 * sum / channels / static_cast<float>(USHRT_MAX);
|
||||
value = 2 * sum / (static_cast<float>(USHRT_MAX) * channels);
|
||||
delete[] channelValues;
|
||||
}
|
||||
|
||||
float Sample::getMonoValue() const
|
||||
float Sample::GetMonoValue() const
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
557
transmitter.cpp
557
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 <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#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<uint32_t>(object) - reinterpret_cast<uint32_t>(peripherals));
|
||||
}
|
||||
inline uint32_t GetVirtualAddress(uint32_t offset) const {
|
||||
return reinterpret_cast<uint32_t>(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<uint32_t>(object) - reinterpret_cast<uint32_t>(memAllocated)) : 0x00000000;
|
||||
}
|
||||
inline uint32_t GetAddress() const {
|
||||
return reinterpret_cast<uint32_t>(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<ClockRegisters *>(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<uint32_t *>(peripherals->GetVirtualAddress(GPIO_BASE_OFFSET));
|
||||
*output = (*output & 0xffff8fff) | (0x04 << 12);
|
||||
#else
|
||||
ClockOutput(unsigned divisor) : ClockDevice(CLK1_BASE_OFFSET, divisor) {
|
||||
output = reinterpret_cast<uint32_t *>(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<unsigned>(Peripherals::GetClockFrequency() * 1000000.f * (0x01 << 12) / (PWM_WRITES_PER_SAMPLE * PWM_CHANNEL_RANGE * sampleRate))) {
|
||||
pwm = reinterpret_cast<PWMRegisters *>(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<DMARegisters *>(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<Sample> 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<uint32_t>(object) - reinterpret_cast<uint32_t>(peripherals));
|
||||
}
|
||||
|
||||
uint32_t Transmitter::getPeripheralVirtAddress(uint32_t offset)
|
||||
{
|
||||
return reinterpret_cast<uint32_t>(peripherals) + offset;
|
||||
}
|
||||
|
||||
uint32_t Transmitter::getMemoryPhysAddress(AllocatedMemory &memory, volatile void *object) const
|
||||
{
|
||||
return memory.physicalBase + (reinterpret_cast<uint32_t>(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<uint32_t>(mapmem(memory.physicalBase & ~0xC0000000, size));
|
||||
memory.size = size;
|
||||
return memory;
|
||||
}
|
||||
|
||||
void Transmitter::freeMemory(AllocatedMemory &memory)
|
||||
{
|
||||
unmapmem(reinterpret_cast<void *>(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<ClockRegisters *>(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<uint32_t>(getSourceFreq() * (0x01 << 12) / pwmClkFreq);
|
||||
pwmClk->ctl = (0x5A << 24) | (0x01 << 4) | 0x06;
|
||||
|
||||
volatile PWMRegisters *pwm = reinterpret_cast<PWMRegisters *>(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<DMARegisters *>(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<ClockRegisters *>(getPeripheralVirtAddress(CLK0_BASE_OFFSET));
|
||||
volatile uint32_t *gpio = reinterpret_cast<uint32_t *>(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<uint32_t>(static_cast<uint64_t>(header.sampleRate) * BUFFER_TIME / 1000000);
|
||||
WaveHeader header = reader.GetHeader();
|
||||
unsigned bufferSize = static_cast<unsigned>(static_cast<unsigned long long>(header.sampleRate) * BUFFER_TIME / 1000000);
|
||||
|
||||
preserveCarrier = preserveCarrierOnExit;
|
||||
clockDivisor = static_cast<uint32_t>(round(getSourceFreq() * (0x01 << 12) / frequency));
|
||||
divisorRange = clockDivisor - static_cast<uint32_t>(round(getSourceFreq() * (0x01 << 12) / (frequency + 0.0005f * bandwidth)));
|
||||
sampleRate = header.sampleRate;
|
||||
unsigned clockDivisor = static_cast<unsigned>(round(Peripherals::GetClockFrequency() * (0x01 << 12) / frequency));
|
||||
unsigned divisorRange = clockDivisor - static_cast<unsigned>(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<Sample> 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<std::mutex> locked(samplesMutex);
|
||||
if (!samples.size()) {
|
||||
if (!reader.setSampleOffset(sampleOffset + bufferSize)) {
|
||||
break;
|
||||
while (!eof && !stopped) {
|
||||
{
|
||||
std::lock_guard<std::mutex> 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<Sample> 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<DMAControllBlock *>(dmaMemory.virtualBase);
|
||||
volatile DMAControllBlock *dmaCb = reinterpret_cast<DMAControllBlock *>(allocated.GetAddress());
|
||||
volatile uint32_t *clkDiv = reinterpret_cast<uint32_t *>(reinterpret_cast<uint32_t>(dmaCb) + 2 * sizeof(DMAControllBlock) * bufferSize);
|
||||
volatile uint32_t *pwmFifoData = reinterpret_cast<uint32_t *>(reinterpret_cast<uint32_t>(clkDiv) + sizeof(uint32_t) * bufferSize);
|
||||
for (i = 0; i < bufferSize; i++) {
|
||||
float value = samples[i].getMonoValue();
|
||||
clkDiv[i] = (0x5A << 24) | (clockDivisor - static_cast<int32_t>(round(value * divisorRange)));
|
||||
float value = samples[i].GetMonoValue();
|
||||
clkDiv[i] = (0x5a << 24) | (0xffffff & (clockDivisor - static_cast<int>(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<int32_t>(round(value * divisorRange)));
|
||||
clkDiv[i] = (0x5a << 24) | (0xffffff & (clockDivisor - static_cast<int>(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<Sample> *samples)
|
||||
{
|
||||
volatile TimerRegisters *timer = reinterpret_cast<TimerRegisters *>(getPeripheralVirtAddress(TIMER_BASE_OFFSET));
|
||||
Peripherals &peripherals = Peripherals::GetInstance();
|
||||
|
||||
volatile TimerRegisters *timer = reinterpret_cast<TimerRegisters *>(peripherals.GetVirtualAddress(TIMER_BASE_OFFSET));
|
||||
uint64_t current = *(reinterpret_cast<volatile uint64_t *>(&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<Sample> loadedSamples;
|
||||
while (true) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(instance->access);
|
||||
if (instance->stopped) {
|
||||
return;
|
||||
}
|
||||
loadedSamples = std::move(*samples);
|
||||
current = *(reinterpret_cast<volatile uint64_t *>(&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<volatile uint64_t *>(&timer->low));
|
||||
locked = samplesMutex.try_lock();
|
||||
}
|
||||
if (!transmitting) {
|
||||
unlock();
|
||||
break;
|
||||
}
|
||||
std::vector<Sample> 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<int32_t>(round(value * divisorRange)));
|
||||
unsigned prevOffset = offset;
|
||||
float value = loadedSamples[offset].GetMonoValue();
|
||||
instance->output->SetDivisor(clockDivisor - static_cast<int>(round(value * divisorRange)));
|
||||
while (offset == prevOffset) {
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(1)); // asm("nop");
|
||||
current = *(reinterpret_cast<volatile uint64_t *>(&timer->low));;
|
||||
|
@ -491,9 +538,3 @@ void Transmitter::transmitThread()
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Transmitter::stop()
|
||||
{
|
||||
preserveCarrier = false;
|
||||
transmitting = false;
|
||||
}
|
||||
|
|
|
@ -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 <mutex>
|
||||
|
||||
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<Sample> *samples);
|
||||
|
||||
bool preserveCarrier;
|
||||
|
||||
static void *peripherals;
|
||||
static bool transmitting;
|
||||
static uint32_t sampleOffset, clockDivisor, divisorRange, sampleRate;
|
||||
static volatile ClockRegisters *output;
|
||||
static std::vector<Sample> samples;
|
||||
static std::mutex samplesMutex;
|
||||
ClockOutput *output;
|
||||
std::mutex access;
|
||||
bool stopped;
|
||||
};
|
||||
|
||||
#endif // TRANSMITTER_HPP
|
||||
|
|
127
wave_reader.cpp
127
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 <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
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<char *>(header.chunkID), 4) != std::string("RIFF")) || (std::string(reinterpret_cast<char *>(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<char *>(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<char *>(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<uint8_t> WaveReader::readData(uint32_t bytesToRead, bool headerBytes, bool &continueFlag)
|
||||
std::string WaveReader::GetFilename() const
|
||||
{
|
||||
uint32_t bytesRead = 0;
|
||||
std::vector<uint8_t> 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<uint32_t>(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<uint8_t *>(&header))[headerOffset], data.data(), bytesRead);
|
||||
headerOffset += bytesRead;
|
||||
} else {
|
||||
currentDataOffset += bytesRead;
|
||||
}
|
||||
|
||||
return data;
|
||||
return fileDescriptor != STDIN_FILENO ? filename : "STDIN";
|
||||
}
|
||||
|
||||
std::vector<Sample> 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<Sample> 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<uint8_t> data = std::move(readData(bytesToRead, false, continueFlag));
|
||||
std::vector<uint8_t> data = std::move(ReadData(bytesToRead, false, stop));
|
||||
if (data.size() < bytesToRead) {
|
||||
quantity = data.size() / bytesPerSample;
|
||||
}
|
||||
|
||||
std::vector<Sample> 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<uint8_t> WaveReader::ReadData(unsigned bytesToRead, bool headerBytes, bool &stop)
|
||||
{
|
||||
return fileDescriptor != STDIN_FILENO ? filename : "STDIN";
|
||||
}
|
||||
unsigned bytesRead = 0;
|
||||
std::vector<uint8_t> 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<unsigned>(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<uint8_t *>(&header))[headerOffset], data.data(), bytesRead);
|
||||
headerOffset += bytesRead;
|
||||
} else {
|
||||
if (stop) {
|
||||
data.resize(bytesRead);
|
||||
}
|
||||
currentDataOffset += bytesRead;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
|
|
@ -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 <string>
|
||||
#include <vector>
|
||||
|
||||
#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<Sample> getSamples(uint32_t quantity, bool &continueFlag);
|
||||
bool setSampleOffset(uint32_t offset);
|
||||
std::string GetFilename() const;
|
||||
const WaveHeader &GetHeader() const;
|
||||
std::vector<Sample> GetSamples(unsigned quantity, bool &stop);
|
||||
bool SetSampleOffset(unsigned offset);
|
||||
private:
|
||||
std::vector<uint8_t> readData(uint32_t bytesToRead, bool headerBytes, bool &continueFlag);
|
||||
std::vector<uint8_t> 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;
|
||||
};
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue