kopia lustrzana https://github.com/markondej/fm_transmitter
Merge 6dfa17b97f
into 00c1337ab0
commit
d6688a0766
|
@ -95,9 +95,9 @@ int main(int argc, char** argv)
|
|||
<< bandwidth << " kHz bandwidth" << std::endl;
|
||||
do {
|
||||
std::string filename = argv[optind++];
|
||||
if ((optind == argc) && loop) {
|
||||
if ((optind == argc) && loop)
|
||||
optind = filesOffset;
|
||||
}
|
||||
|
||||
WaveReader reader(filename != "-" ? filename : std::string(), enable, mtx);
|
||||
WaveHeader header = reader.GetHeader();
|
||||
std::cout << "Playing: " << reader.GetFilename() << ", "
|
||||
|
|
|
@ -169,21 +169,18 @@ class Peripherals
|
|||
private:
|
||||
Peripherals() {
|
||||
int memFd;
|
||||
if ((memFd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) {
|
||||
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) {
|
||||
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 == BCM2711_PERI_VIRT_BASE) {
|
||||
if (size == BCM2711_PERI_VIRT_BASE)
|
||||
size = 0x01000000;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
|
@ -197,9 +194,8 @@ class AllocatedMemory
|
|||
AllocatedMemory(unsigned size) {
|
||||
mBoxFd = mbox_open();
|
||||
memSize = size;
|
||||
if (memSize % PAGE_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 : BCM2711_MEM_FLAG);
|
||||
if (!memHandle) {
|
||||
mbox_close(mBoxFd);
|
||||
|
@ -353,10 +349,9 @@ Transmitter::~Transmitter() {
|
|||
cv.wait(lock, [&]() -> bool {
|
||||
return !enable;
|
||||
});
|
||||
if (output) {
|
||||
if (output)
|
||||
delete output;
|
||||
}
|
||||
}
|
||||
|
||||
void Transmitter::Transmit(WaveReader &reader, float frequency, float bandwidth, unsigned dmaChannel, bool preserveCarrier)
|
||||
{
|
||||
|
@ -383,15 +378,13 @@ void Transmitter::Transmit(WaveReader &reader, float frequency, float bandwidth,
|
|||
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) {
|
||||
if (!output)
|
||||
output = new ClockOutput(clockDivisor);
|
||||
}
|
||||
|
||||
if (dmaChannel != 0xff) {
|
||||
if (dmaChannel != 0xff)
|
||||
TxViaDma(reader, header.sampleRate, bufferSize, clockDivisor, divisorRange, dmaChannel);
|
||||
} else {
|
||||
else
|
||||
TxViaCpu(reader, header.sampleRate, bufferSize, clockDivisor, divisorRange);
|
||||
}
|
||||
} catch (...) {
|
||||
finally();
|
||||
throw;
|
||||
|
@ -409,16 +402,14 @@ void Transmitter::Stop()
|
|||
|
||||
void Transmitter::TxViaDma(WaveReader &reader, unsigned sampleRate, unsigned bufferSize, unsigned clockDivisor, unsigned divisorRange, unsigned dmaChannel)
|
||||
{
|
||||
if (dmaChannel > 15) {
|
||||
if (dmaChannel > 15)
|
||||
throw std::runtime_error("DMA channel number out of range (0 - 15)");
|
||||
}
|
||||
|
||||
AllocatedMemory allocated(sizeof(uint32_t) * bufferSize + sizeof(DMAControllBlock) * (2 * bufferSize) + sizeof(uint32_t));
|
||||
|
||||
std::vector<Sample> samples = reader.GetSamples(bufferSize, enable, mtx);
|
||||
if (samples.empty()) {
|
||||
if (samples.empty())
|
||||
return;
|
||||
}
|
||||
|
||||
bool eof = false;
|
||||
if (samples.size() < bufferSize) {
|
||||
|
@ -461,30 +452,27 @@ void Transmitter::TxViaDma(WaveReader &reader, unsigned sampleRate, unsigned buf
|
|||
|
||||
auto finally = [&]() {
|
||||
dmaCb[(cbOffset < 2 * bufferSize) ? cbOffset : 0].nextCbAddress = 0x00000000;
|
||||
while (dma.GetControllBlockAddress() != 0x00000000) {
|
||||
while (dma.GetControllBlockAddress() != 0x00000000)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
samples.clear();
|
||||
};
|
||||
try {
|
||||
while (!eof) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
if (!enable) {
|
||||
if (!enable)
|
||||
break;
|
||||
}
|
||||
}
|
||||
samples = reader.GetSamples(bufferSize, enable, mtx);
|
||||
if (!samples.size()) {
|
||||
if (!samples.size())
|
||||
break;
|
||||
}
|
||||
|
||||
cbOffset = 0;
|
||||
eof = samples.size() < bufferSize;
|
||||
for (std::size_t i = 0; i < samples.size(); i++) {
|
||||
float value = samples[i].GetMonoValue();
|
||||
while (i == ((dma.GetControllBlockAddress() - allocated.GetPhysicalAddress(dmaCb)) / (2 * sizeof(DMAControllBlock)))) {
|
||||
while (i == ((dma.GetControllBlockAddress() - allocated.GetPhysicalAddress(dmaCb)) / (2 * sizeof(DMAControllBlock))))
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(BUFFER_TIME / 10));
|
||||
}
|
||||
clkDiv[i] = CLK_PASSWORD | (0xffffff & (clockDivisor - static_cast<int>(round(value * divisorRange))));
|
||||
cbOffset += 2;
|
||||
}
|
||||
|
@ -523,28 +511,28 @@ void Transmitter::TxViaCpu(WaveReader &reader, unsigned sampleRate, unsigned buf
|
|||
return samples.empty() || !enable || stop;
|
||||
});
|
||||
}
|
||||
if (!enable) {
|
||||
|
||||
if (!enable)
|
||||
break;
|
||||
}
|
||||
if (stop) {
|
||||
|
||||
if (stop)
|
||||
throw std::runtime_error("Transmitter thread has unexpectedly exited");
|
||||
}
|
||||
|
||||
if (samples.empty()) {
|
||||
if (!reader.SetSampleOffset(sampleOffset + (start ? 0 : bufferSize))) {
|
||||
if (!reader.SetSampleOffset(sampleOffset + (start ? 0 : bufferSize)))
|
||||
break;
|
||||
}
|
||||
lock.unlock();
|
||||
samples = reader.GetSamples(bufferSize, enable, mtx);
|
||||
lock.lock();
|
||||
if (samples.empty()) {
|
||||
if (samples.empty())
|
||||
break;
|
||||
}
|
||||
|
||||
eof = samples.size() < bufferSize;
|
||||
lock.unlock();
|
||||
cv.notify_all();
|
||||
} else {
|
||||
} else
|
||||
lock.unlock();
|
||||
}
|
||||
|
||||
start = false;
|
||||
}
|
||||
} catch (...) {
|
||||
|
@ -567,9 +555,10 @@ void Transmitter::CpuTxThread(unsigned sampleRate, unsigned clockDivisor, unsign
|
|||
cv.wait(lock, [&]() -> bool {
|
||||
return !samples->empty() || *stop;
|
||||
});
|
||||
if (*stop) {
|
||||
|
||||
if (*stop)
|
||||
break;
|
||||
}
|
||||
|
||||
start = current = std::chrono::system_clock::now();
|
||||
*sampleOffset = std::chrono::duration_cast<std::chrono::microseconds>(current - playbackStart).count() * sampleRate / 1000000;
|
||||
loadedSamples = std::move(*samples);
|
||||
|
@ -579,9 +568,9 @@ void Transmitter::CpuTxThread(unsigned sampleRate, unsigned clockDivisor, unsign
|
|||
unsigned offset = 0;
|
||||
|
||||
while (true) {
|
||||
if (offset >= loadedSamples.size()) {
|
||||
if (offset >= loadedSamples.size())
|
||||
break;
|
||||
}
|
||||
|
||||
unsigned prevOffset = offset;
|
||||
float value = loadedSamples[offset].GetMonoValue();
|
||||
output->SetDivisor(clockDivisor - static_cast<int>(round(value * divisorRange)));
|
||||
|
|
125
wave_reader.cpp
125
wave_reader.cpp
|
@ -43,21 +43,18 @@
|
|||
Sample::Sample(uint8_t *data, unsigned channels, unsigned bitsPerChannel)
|
||||
: value(0.f)
|
||||
{
|
||||
int sum = 0;
|
||||
int16_t *channelValues = new int16_t[channels];
|
||||
int32_t sum = 0;
|
||||
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];
|
||||
sum += *reinterpret_cast<int16_t *>(&data[i << 1]);
|
||||
break;
|
||||
case 1:
|
||||
channelValues[i] = (static_cast<int16_t>(data[i]) - 0x80) << 8;
|
||||
sum += (static_cast<int16_t>(data[i]) - 0x80) << 8;
|
||||
break;
|
||||
}
|
||||
sum += channelValues[i];
|
||||
}
|
||||
value = 2 * sum / (static_cast<float>(USHRT_MAX) * channels);
|
||||
delete[] channelValues;
|
||||
value = sum / (-static_cast<float>(SHRT_MIN) * channels);
|
||||
}
|
||||
|
||||
float Sample::GetMonoValue() const
|
||||
|
@ -66,63 +63,55 @@ float Sample::GetMonoValue() const
|
|||
}
|
||||
|
||||
WaveReader::WaveReader(const std::string &filename, bool &enable, std::mutex &mtx) :
|
||||
filename(filename), headerOffset(0), currentDataOffset(0)
|
||||
filename(filename)
|
||||
{
|
||||
if (!filename.empty()) {
|
||||
if (!filename.empty())
|
||||
fileDescriptor = open(filename.c_str(), O_RDONLY);
|
||||
} else {
|
||||
else {
|
||||
fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL, 0) | O_NONBLOCK);
|
||||
fileDescriptor = STDIN_FILENO;
|
||||
}
|
||||
|
||||
if (fileDescriptor == -1) {
|
||||
throw std::runtime_error(std::string("Cannot open ") + GetFilename() + std::string(", file does not exist"));
|
||||
}
|
||||
if (fileDescriptor == -1)
|
||||
throw std::runtime_error(std::string("Cannot open ") + GetFilename());
|
||||
|
||||
try {
|
||||
ReadData(sizeof(WaveHeader::chunkID) + sizeof(WaveHeader::chunkSize) + sizeof(WaveHeader::format), true, enable, mtx);
|
||||
if ((std::string(reinterpret_cast<char *>(header.chunkID), 4) != std::string("RIFF")) || (std::string(reinterpret_cast<char *>(header.format), 4) != std::string("WAVE"))) {
|
||||
ReadData(&header.chunkID, sizeof(WaveHeader::chunkID) + sizeof(WaveHeader::chunkSize) + sizeof(WaveHeader::format), enable, mtx);
|
||||
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"));
|
||||
}
|
||||
|
||||
ReadData(sizeof(WaveHeader::subchunk1ID) + sizeof(WaveHeader::subchunk1Size), true, enable, mtx);
|
||||
ReadData(&header.subchunk1ID, sizeof(WaveHeader::subchunk1ID) + sizeof(WaveHeader::subchunk1Size), enable, mtx);
|
||||
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)) {
|
||||
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"));
|
||||
}
|
||||
|
||||
ReadData(header.subchunk1Size, true, enable, mtx);
|
||||
ReadData(&header.audioFormat, header.subchunk1Size, enable, mtx);
|
||||
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))) {
|
||||
(((header.bitsPerSample >> 3) != 1) && ((header.bitsPerSample >> 3) != 2)))
|
||||
throw std::runtime_error(std::string("Error while opening ") + GetFilename() + std::string(", unsupported WAVE format"));
|
||||
}
|
||||
|
||||
ReadData(sizeof(WaveHeader::subchunk2ID) + sizeof(WaveHeader::subchunk2Size), true, enable, mtx);
|
||||
if (std::string(reinterpret_cast<char *>(header.subchunk2ID), 4) != std::string("data")) {
|
||||
ReadData(&header.subchunk2ID, sizeof(WaveHeader::subchunk2ID) + sizeof(WaveHeader::subchunk2Size), enable, mtx);
|
||||
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"));
|
||||
}
|
||||
} catch (...) {
|
||||
if (fileDescriptor != STDIN_FILENO) {
|
||||
if (fileDescriptor != STDIN_FILENO)
|
||||
close(fileDescriptor);
|
||||
}
|
||||
throw;
|
||||
}
|
||||
|
||||
if (fileDescriptor != STDIN_FILENO) {
|
||||
if (fileDescriptor != STDIN_FILENO)
|
||||
dataOffset = lseek(fileDescriptor, 0, SEEK_CUR);
|
||||
}
|
||||
}
|
||||
|
||||
WaveReader::~WaveReader()
|
||||
{
|
||||
if (fileDescriptor != STDIN_FILENO) {
|
||||
if (fileDescriptor != STDIN_FILENO)
|
||||
close(fileDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
std::string WaveReader::GetFilename() const
|
||||
{
|
||||
|
@ -137,96 +126,66 @@ const WaveHeader &WaveReader::GetHeader() const
|
|||
std::vector<Sample> WaveReader::GetSamples(unsigned quantity, bool &enable, std::mutex &mtx) {
|
||||
unsigned bytesPerSample = (header.bitsPerSample >> 3) * header.channels;
|
||||
unsigned bytesToRead = quantity * bytesPerSample;
|
||||
unsigned bytesLeft = header.subchunk2Size - currentDataOffset;
|
||||
unsigned bytesLeft = (fileDescriptor != STDIN_FILENO) ?
|
||||
header.subchunk2Size - lseek(fileDescriptor, 0, SEEK_CUR) : bytesToRead;
|
||||
if (bytesToRead > bytesLeft) {
|
||||
bytesToRead = bytesLeft - bytesLeft % bytesPerSample;
|
||||
quantity = bytesToRead / bytesPerSample;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> data = std::move(ReadData(bytesToRead, false, enable, mtx));
|
||||
if (data.size() < bytesToRead) {
|
||||
std::vector<uint8_t> data(bytesToRead);
|
||||
data.resize(ReadData(&data[0], bytesToRead, enable, mtx));
|
||||
if (data.size() < bytesToRead)
|
||||
quantity = data.size() / bytesPerSample;
|
||||
}
|
||||
|
||||
std::vector<Sample> samples;
|
||||
samples.reserve(quantity);
|
||||
for (unsigned 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(unsigned offset) {
|
||||
if (fileDescriptor != STDIN_FILENO) {
|
||||
currentDataOffset = offset * (header.bitsPerSample >> 3) * header.channels;
|
||||
if (lseek(fileDescriptor, dataOffset + currentDataOffset, SEEK_SET) == -1) {
|
||||
if (fileDescriptor != STDIN_FILENO)
|
||||
if (lseek(fileDescriptor, dataOffset + offset * (header.bitsPerSample >> 3) * header.channels, SEEK_SET) == -1)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> WaveReader::ReadData(unsigned bytesToRead, bool headerBytes, bool &enable, std::mutex &mtx)
|
||||
unsigned WaveReader::ReadData(void *buffer, unsigned bytesToRead, bool &enable, std::mutex &mtx)
|
||||
{
|
||||
unsigned bytesRead = 0;
|
||||
std::vector<uint8_t> data;
|
||||
data.resize(bytesToRead);
|
||||
timeval timeout = {
|
||||
.tv_sec = 1,
|
||||
};
|
||||
fd_set fds;
|
||||
|
||||
while (bytesRead < bytesToRead) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
if (!enable) {
|
||||
if (!enable)
|
||||
break;
|
||||
}
|
||||
}
|
||||
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) {
|
||||
int bytes = read(fileDescriptor, &(reinterpret_cast<uint8_t *>(buffer)[bytesRead]), bytesToRead - bytesRead);
|
||||
if ((bytes == -1) && ((fileDescriptor != STDIN_FILENO) || (errno != EAGAIN)))
|
||||
throw std::runtime_error(std::string("Error while opening ") + GetFilename() + std::string(", cannot read file"));
|
||||
if (bytes == 0)
|
||||
break;
|
||||
if (bytes > 0)
|
||||
bytesRead += bytes;
|
||||
}
|
||||
if (bytesRead < bytesToRead) {
|
||||
if (fileDescriptor != STDIN_FILENO) {
|
||||
data.resize(bytesRead);
|
||||
if (fileDescriptor != STDIN_FILENO)
|
||||
break;
|
||||
} else {
|
||||
else {
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(STDIN_FILENO, &fds);
|
||||
select(STDIN_FILENO + 1, &fds, nullptr, nullptr, &timeout);
|
||||
if (FD_ISSET(STDIN_FILENO, &fds)) {
|
||||
if (FD_ISSET(STDIN_FILENO, &fds))
|
||||
FD_CLR(STDIN_FILENO, &fds);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bytes == 0) {
|
||||
data.resize(bytesRead);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (headerBytes) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
if (!enable) {
|
||||
throw std::runtime_error("Cannot obtain header, program interrupted");
|
||||
}
|
||||
}
|
||||
std::memcpy(&(reinterpret_cast<uint8_t *>(&header))[headerOffset], data.data(), bytesRead);
|
||||
headerOffset += bytesRead;
|
||||
} else {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx);
|
||||
if (!enable) {
|
||||
data.resize(bytesRead);
|
||||
}
|
||||
}
|
||||
currentDataOffset += bytesRead;
|
||||
}
|
||||
|
||||
return data;
|
||||
return bytesRead;
|
||||
}
|
||||
|
|
|
@ -79,10 +79,10 @@ class WaveReader
|
|||
std::vector<Sample> GetSamples(unsigned quantity, bool &enable, std::mutex &mtx);
|
||||
bool SetSampleOffset(unsigned offset);
|
||||
private:
|
||||
std::vector<uint8_t> ReadData(unsigned bytesToRead, bool headerBytes, bool &enable, std::mutex &mtx);
|
||||
unsigned ReadData(void *buffer, unsigned bytesToRead, bool &enable, std::mutex &mtx);
|
||||
|
||||
std::string filename;
|
||||
WaveHeader header;
|
||||
unsigned dataOffset, headerOffset, currentDataOffset;
|
||||
unsigned dataOffset;
|
||||
int fileDescriptor;
|
||||
};
|
||||
|
|
Ładowanie…
Reference in New Issue