diff --git a/README.md b/README.md index 245960e..697fab8 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ make ``` After a successful build you can start transmitting by executing the "fm_transmitter" program: ``` -sudo ./fm_transmitter -f 102.0 acoustic_guitar_duet.wav +sudo ./fm_transmitter -f 100.6 acoustic_guitar_duet.wav ``` Where: * -f frequency - Specifies the frequency in MHz, 100.0 by default if not passed @@ -33,7 +33,7 @@ Other options: * -b bandwidth - Specifies the bandwidth in kHz, 100 by default * -r - Loops the playback -After transmission has begun, simply tune an FM receiver to chosen frequency, You should hear 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): @@ -45,6 +45,22 @@ make GPIO21=1 echo "performance"| sudo tee /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor ``` 3. Using lower FM broadcasting frequencies (below 93 MHz) when transmitting. +### Use as general audio output device +[hydranix](https://github.com/markondej/fm_transmitter/issues/144) has came up with simple method of using transmitter as an general audio output device. In order to achieve this you should load "snd-aloop" module and stream output from loopback device to transmitter application: +``` +sudo modprobe snd-aloop +arecord -D hw:1,1,0 -c 1 -d 0 -r 22050 -f S16_LE | sudo ./fm_transmitter -f 100.6 - & +``` +Please keep in mind loopback device should be set default ALSA device (see [this article](https://www.alsa-project.org/wiki/Setting_the_default_device)). Also parameter "-D hw:X,1,0" should be pointing this device (use card number instead of "X"). +### Microphone support +In order to use a microphone live input use the `arecord` command, eg.: +``` +arecord -D hw:1,0 -c 1 -d 0 -r 22050 -f S16_LE | sudo ./fm_transmitter -f 100.6 - +``` +In cases of a performance drop down use ```plughw:1,0``` instead of ```hw:1,0``` like this: +``` +arecord -D plughw:1,0 -c 1 -d 0 -r 22050 -f S16_LE | sudo ./fm_transmitter -f 100.6 - +``` ### Supported audio formats You can transmitt uncompressed WAV (.wav) files directly or read audio data from stdin, eg. using MP3 file: ``` @@ -62,15 +78,6 @@ Or you could also use FFMPEG: ffmpeg -i example.webm -f wav -bitexact -acodec pcm_s16le -ar 22050 -ac 1 converted-example.wav sudo ./fm_transmitter -f 100.6 converted-example.wav ``` -### Microphone support -In order to use a microphone live input use the `arecord` command, eg.: -``` -arecord -D hw:1,0 -c1 -d 0 -r 22050 -f S16_LE | sudo ./fm_transmitter -f 100.6 - -``` -In cases of a performance drop down use ```plughw:1,0``` instead of ```hw:1,0``` like this: -``` -arecord -D plughw:1,0 -c1 -d 0 -r 22050 -f S16_LE | sudo ./fm_transmitter -f 100.6 - -``` ## Legal note Please keep in mind that transmitting on certain frequencies without special permissions may be illegal in your country. ## New features diff --git a/fm_transmitter.cpp b/fm_transmitter.cpp index daebeb2..b5b158a 100644 --- a/fm_transmitter.cpp +++ b/fm_transmitter.cpp @@ -109,9 +109,11 @@ int main(int argc, char** argv) std::cout << "Error: " << catched.what() << std::endl; result = EXIT_FAILURE; } - auto temp = transmitter; - transmitter = nullptr; - delete temp; + if (transmitter != nullptr) { + auto temp = transmitter; + transmitter = nullptr; + delete temp; + } return result; } diff --git a/makefile b/makefile index a714399..e187b65 100644 --- a/makefile +++ b/makefile @@ -1,20 +1,17 @@ EXECUTABLE = fm_transmitter -VERSION = 0.9.5 +VERSION = 0.9.6 FLAGS = -Wall -O3 -std=c++11 TRANSMITTER = -fno-strict-aliasing -I/opt/vc/include ifeq ($(GPIO21), 1) TRANSMITTER += -DGPIO21 endif -all: fm_transmitter.o mailbox.o sample.o wave_reader.o transmitter.o - g++ -L/opt/vc/lib -o $(EXECUTABLE) fm_transmitter.o mailbox.o sample.o wave_reader.o transmitter.o -lm -lpthread -lbcm_host +all: fm_transmitter.o mailbox.o wave_reader.o transmitter.o + g++ -L/opt/vc/lib -o $(EXECUTABLE) fm_transmitter.o mailbox.o wave_reader.o transmitter.o -lm -lpthread -lbcm_host mailbox.o: mailbox.c mailbox.h g++ $(FLAGS) -c mailbox.c -sample.o: sample.cpp sample.hpp - g++ $(FLAGS) -c sample.cpp - wave_reader.o: wave_reader.cpp wave_reader.hpp g++ $(FLAGS) -c wave_reader.cpp diff --git a/sample.cpp b/sample.cpp deleted file mode 100644 index 1a7ed6b..0000000 --- a/sample.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/* - FM Transmitter - use Raspberry Pi as FM transmitter - - Copyright (c) 2021, 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. -*/ - -#include "sample.hpp" -#include - -Sample::Sample(uint8_t *data, unsigned channels, unsigned bitsPerChannel) - : value(0.f) -{ - int sum = 0; - int16_t *channelValues = new int16_t[channels]; - 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 / (static_cast(USHRT_MAX) * channels); - delete[] channelValues; -} - -float Sample::GetMonoValue() const -{ - return value; -} diff --git a/sample.hpp b/sample.hpp deleted file mode 100644 index 3d8414b..0000000 --- a/sample.hpp +++ /dev/null @@ -1,48 +0,0 @@ -/* - FM Transmitter - use Raspberry Pi as FM transmitter - - Copyright (c) 2021, 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 SAMPLE_HPP -#define SAMPLE_HPP - -#include - -class Sample -{ - public: - Sample(uint8_t *data, unsigned channels, unsigned bitsPerChannel); - float GetMonoValue() const; - protected: - float value; -}; - -#endif // SAMPLE_HPP diff --git a/transmitter.cpp b/transmitter.cpp index 5679d90..96caa3f 100644 --- a/transmitter.cpp +++ b/transmitter.cpp @@ -353,10 +353,8 @@ class DMAController : public Device volatile DMARegisters *dma; }; -bool Transmitter::transmitting = false; - Transmitter::Transmitter() - : output(nullptr), stopped(true) + : output(nullptr), stop(true) { } @@ -368,11 +366,7 @@ Transmitter::~Transmitter() { void Transmitter::Transmit(WaveReader &reader, float frequency, float bandwidth, unsigned dmaChannel, bool preserveCarrier) { - if (transmitting) { - throw std::runtime_error("Cannot transmit, transmitter already in use"); - } - transmitting = true; - stopped = false; + stop = false; WaveHeader header = reader.GetHeader(); unsigned bufferSize = static_cast(static_cast(header.sampleRate) * BUFFER_TIME / 1000000); @@ -389,7 +383,6 @@ void Transmitter::Transmit(WaveReader &reader, float frequency, float bandwidth, delete output; output = nullptr; } - transmitting = false; }; try { if (dmaChannel != 0xff) { @@ -406,36 +399,43 @@ void Transmitter::Transmit(WaveReader &reader, float frequency, float bandwidth, void Transmitter::Stop() { - stopped = true; + stop = true; } void Transmitter::TransmitViaCpu(WaveReader &reader, ClockOutput &output, unsigned sampleRate, unsigned bufferSize, unsigned clockDivisor, unsigned divisorRange) { - std::vector samples = reader.GetSamples(bufferSize, stopped); + std::vector samples = reader.GetSamples(bufferSize, stop); if (samples.empty()) { return; } unsigned sampleOffset = 0; - bool eof = samples.size() < bufferSize; - std::thread transmitterThread(Transmitter::TransmitterThread, this, &output, sampleRate, clockDivisor, divisorRange, &sampleOffset, &samples); + bool eof = samples.size() < bufferSize, txStop = false; + std::thread transmitterThread(Transmitter::TransmitterThread, this, &output, sampleRate, clockDivisor, divisorRange, &sampleOffset, &samples, &txStop); std::this_thread::sleep_for(std::chrono::microseconds(BUFFER_TIME / 2)); auto finally = [&]() { - stopped = true; + { + std::lock_guard lock(access); + txStop = true; + } transmitterThread.join(); samples.clear(); + stop = true; }; try { - while (!eof && !stopped) { + while (!eof && !stop) { { std::lock_guard lock(access); + if (txStop) { + throw std::runtime_error("Transmitter thread has unexpectedly exited"); + } if (samples.empty()) { if (!reader.SetSampleOffset(sampleOffset + bufferSize)) { break; } - samples = reader.GetSamples(bufferSize, stopped); + samples = reader.GetSamples(bufferSize, stop); if (samples.empty()) { break; } @@ -459,7 +459,7 @@ void Transmitter::TransmitViaDma(WaveReader &reader, ClockOutput &output, unsign AllocatedMemory allocated(sizeof(uint32_t) * bufferSize + sizeof(DMAControllBlock) * (2 * bufferSize) + sizeof(uint32_t)); - std::vector samples = reader.GetSamples(bufferSize, stopped); + std::vector samples = reader.GetSamples(bufferSize, stop); if (samples.empty()) { return; } @@ -508,12 +508,12 @@ void Transmitter::TransmitViaDma(WaveReader &reader, ClockOutput &output, unsign while (dma.GetControllBlockAddress() != 0x00000000) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); } - stopped = true; samples.clear(); + stop = true; }; try { - while (!eof && !stopped) { - samples = reader.GetSamples(bufferSize, stopped); + while (!eof && !stop) { + samples = reader.GetSamples(bufferSize, stop); if (!samples.size()) { break; } @@ -535,47 +535,52 @@ void Transmitter::TransmitViaDma(WaveReader &reader, ClockOutput &output, unsign finally(); } -void Transmitter::TransmitterThread(Transmitter *instance, ClockOutput *output, unsigned sampleRate, unsigned clockDivisor, unsigned divisorRange, unsigned *sampleOffset, std::vector *samples) +void Transmitter::TransmitterThread(Transmitter *instance, ClockOutput *output, unsigned sampleRate, unsigned clockDivisor, unsigned divisorRange, unsigned *sampleOffset, std::vector *samples, bool *stop) { - Peripherals &peripherals = Peripherals::GetInstance(); + try { + 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; + volatile TimerRegisters *timer = reinterpret_cast(peripherals.GetVirtualAddress(TIMER_BASE_OFFSET)); + uint64_t current = *(reinterpret_cast(&timer->low)); + uint64_t playbackStart = current; - while (true) { - std::vector loadedSamples; while (true) { - { - std::lock_guard lock(instance->access); - if (instance->stopped) { - return; + std::vector loadedSamples; + while (true) { + { + std::lock_guard lock(instance->access); + if (*stop) { + return; + } + loadedSamples = std::move(*samples); + current = *(reinterpret_cast(&timer->low)); + if (!loadedSamples.empty()) { + *sampleOffset = (current - playbackStart) * sampleRate / 1000000; + break; + } } - loadedSamples = std::move(*samples); - current = *(reinterpret_cast(&timer->low)); - if (!loadedSamples.empty()) { - *sampleOffset = (current - playbackStart) * sampleRate / 1000000; + std::this_thread::sleep_for(std::chrono::microseconds(1)); + }; + + uint64_t start = current; + unsigned offset = (current - start) * sampleRate / 1000000; + + while (true) { + if (offset >= loadedSamples.size()) { break; } - } - std::this_thread::sleep_for(std::chrono::microseconds(1)); - }; - - uint64_t start = current; - unsigned offset = (current - start) * sampleRate / 1000000; - - while (true) { - if (offset >= loadedSamples.size()) { - break; - } - 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));; - offset = (current - start) * sampleRate / 1000000; + 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));; + offset = (current - start) * sampleRate / 1000000; + } } } + } catch (...) { + std::lock_guard lock(instance->access); + *stop = true; } } diff --git a/transmitter.hpp b/transmitter.hpp index 9f10890..74a2e77 100644 --- a/transmitter.hpp +++ b/transmitter.hpp @@ -31,8 +31,7 @@ WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef TRANSMITTER_HPP -#define TRANSMITTER_HPP +#pragma once #include "wave_reader.hpp" #include @@ -52,12 +51,9 @@ class Transmitter private: 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); + static void TransmitterThread(Transmitter *instance, ClockOutput *output, unsigned sampleRate, unsigned clockDivisor, unsigned divisorRange, unsigned *sampleOffset, std::vector *samples, bool *stop); - static bool transmitting; ClockOutput *output; std::mutex access; - bool stopped; + bool stop; }; - -#endif // TRANSMITTER_HPP diff --git a/wave_reader.cpp b/wave_reader.cpp index 10d694d..72eca11 100644 --- a/wave_reader.cpp +++ b/wave_reader.cpp @@ -38,6 +38,32 @@ #include #include #include +#include + +Sample::Sample(uint8_t *data, unsigned channels, unsigned bitsPerChannel) + : value(0.f) +{ + int sum = 0; + int16_t *channelValues = new int16_t[channels]; + 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 / (static_cast(USHRT_MAX) * channels); + delete[] channelValues; +} + +float Sample::GetMonoValue() const +{ + return value; +} WaveReader::WaveReader(const std::string &filename, bool &stop) : filename(filename), headerOffset(0), currentDataOffset(0) diff --git a/wave_reader.hpp b/wave_reader.hpp index 6e30747..2e2a54c 100644 --- a/wave_reader.hpp +++ b/wave_reader.hpp @@ -31,10 +31,9 @@ WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef WAVE_READER_HPP -#define WAVE_READER_HPP +#pragma once -#include "sample.hpp" +#include #include #include @@ -57,6 +56,15 @@ struct WaveHeader uint32_t subchunk2Size; }; +class Sample +{ +public: + Sample(uint8_t *data, unsigned channels, unsigned bitsPerChannel); + float GetMonoValue() const; +protected: + float value; +}; + class WaveReader { public: @@ -77,5 +85,3 @@ class WaveReader unsigned dataOffset, headerOffset, currentDataOffset; int fileDescriptor; }; - -#endif // WAVE_READER_HPP