Added instructions for general audio broadcasting (issue #144)

resampler
Marcin Kondej 2022-02-08 12:49:20 +01:00
rodzic 245e099f0d
commit 0c1966aa51
9 zmienionych plików z 124 dodań i 193 usunięć

Wyświetl plik

@ -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

Wyświetl plik

@ -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;
}

Wyświetl plik

@ -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

Wyświetl plik

@ -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 <climits>
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<int16_t>(data[i]) - 0x80) << 8;
break;
}
sum += channelValues[i];
}
value = 2 * sum / (static_cast<float>(USHRT_MAX) * channels);
delete[] channelValues;
}
float Sample::GetMonoValue() const
{
return value;
}

Wyświetl plik

@ -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 <cstdint>
class Sample
{
public:
Sample(uint8_t *data, unsigned channels, unsigned bitsPerChannel);
float GetMonoValue() const;
protected:
float value;
};
#endif // SAMPLE_HPP

Wyświetl plik

@ -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<unsigned>(static_cast<unsigned long long>(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<Sample> samples = reader.GetSamples(bufferSize, stopped);
std::vector<Sample> 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<std::mutex> lock(access);
txStop = true;
}
transmitterThread.join();
samples.clear();
stop = true;
};
try {
while (!eof && !stopped) {
while (!eof && !stop) {
{
std::lock_guard<std::mutex> 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<Sample> samples = reader.GetSamples(bufferSize, stopped);
std::vector<Sample> 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<Sample> *samples)
void Transmitter::TransmitterThread(Transmitter *instance, ClockOutput *output, unsigned sampleRate, unsigned clockDivisor, unsigned divisorRange, unsigned *sampleOffset, std::vector<Sample> *samples, bool *stop)
{
Peripherals &peripherals = Peripherals::GetInstance();
try {
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;
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 (true) {
std::vector<Sample> loadedSamples;
while (true) {
{
std::lock_guard<std::mutex> lock(instance->access);
if (instance->stopped) {
return;
std::vector<Sample> loadedSamples;
while (true) {
{
std::lock_guard<std::mutex> lock(instance->access);
if (*stop) {
return;
}
loadedSamples = std::move(*samples);
current = *(reinterpret_cast<volatile uint64_t *>(&timer->low));
if (!loadedSamples.empty()) {
*sampleOffset = (current - playbackStart) * sampleRate / 1000000;
break;
}
}
loadedSamples = std::move(*samples);
current = *(reinterpret_cast<volatile uint64_t *>(&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<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));;
offset = (current - start) * sampleRate / 1000000;
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));;
offset = (current - start) * sampleRate / 1000000;
}
}
}
} catch (...) {
std::lock_guard<std::mutex> lock(instance->access);
*stop = true;
}
}

Wyświetl plik

@ -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 <mutex>
@ -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<Sample> *samples);
static void TransmitterThread(Transmitter *instance, ClockOutput *output, unsigned sampleRate, unsigned clockDivisor, unsigned divisorRange, unsigned *sampleOffset, std::vector<Sample> *samples, bool *stop);
static bool transmitting;
ClockOutput *output;
std::mutex access;
bool stopped;
bool stop;
};
#endif // TRANSMITTER_HPP

Wyświetl plik

@ -38,6 +38,32 @@
#include <chrono>
#include <unistd.h>
#include <fcntl.h>
#include <climits>
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<int16_t>(data[i]) - 0x80) << 8;
break;
}
sum += channelValues[i];
}
value = 2 * sum / (static_cast<float>(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)

Wyświetl plik

@ -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 <cstdint>
#include <string>
#include <vector>
@ -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