Fixed issue #34, #23 - added DMA support

Fixed issue #56 - added support for playlsits
pull/69/head
Marcin Kondej 2019-01-05 04:06:16 +01:00
rodzic ea843abcbc
commit 11fcc4c35b
16 zmienionych plików z 1038 dodań i 308 usunięć

Wyświetl plik

@ -1,7 +1,7 @@
# fm_transmitter
Use Raspberry Pi as FM transmitter. Works on any Raspberry Pi board.
This project uses the general clock output to produce frequency modulated radio communication. It is based on idea originaly posted here: [http://icrobotics.co.uk/wiki/index.php/Turning_the_Raspberry_Pi_Into_an_FM_Transmitter](http://icrobotics.co.uk/wiki/index.php/Turning_the_Raspberry_Pi_Into_an_FM_Transmitter), but does not use DMA controller in order to distribute samples to output (clock generator), so sound quality is worse as in PiFm project and only mono transmition is available but this makes possible to run it on all kind of boards.
This project uses the general clock output to produce frequency modulated radio communication. It is based on idea originaly presented by Oliver Mattos and Oskar Weigl on [http://icrobotics.co.uk/wiki/index.php/Turning_the_Raspberry_Pi_Into_an_FM_Transmitter](PiFM project).
## How to use it
To use this project You will have to build it. First, clone this repository, then use "make" command as shown below:
@ -15,9 +15,13 @@ After successful build You can start transmitting by typing:
sudo ./fm_transmitter -f 102.0 acoustic_guitar_duet.wav
```
Where:
* -f 102.0 - Specifies the frequency in MHz, if not passed default is 100.0
* -f <frequency> - Specifies the frequency in MHz, 100.0 by default if not passed
* acoustic_guitar_duet.wav - Sample WAVE file, You can use your own
Other options:
* -d <dma_channel> - Specifies the used DMA channel (0 by default), pass 255 in order to disable DMA and use CPU
* -r - Loops the playback
### Supported audio formats
You can transmitt uncompressed WAVE (.wav) files directly or read audio data from stdin, eg.:
```
@ -28,8 +32,8 @@ Please note only uncompressed WAVE files are supported. If You expire "corrupted
sox my-audio.mp3 -r 22050 -c 1 -b 16 -t wav my-converted-audio.wav
sudo ./fm_transmitter -f 100.6 my-converted-audio.wav
```
### USB microphone
To use a USB sound card microphone input use arecord, eg.:
### USB microphone support
In order to use a USB microphone input use arecord command, eg.:
```
arecord -D hw:1,0 -c1 -d 0 -r 22050 -f S16_LE | sudo ./fm_transmitter -f 100.6 -
```
@ -39,9 +43,9 @@ In case of performance drop down use ```plughw:1,0``` instead of ```hw:1,0```.
Please keep in mind that transmitting on certain frequencies without special permissions may be illegal in your country.
## New features
* DMA engine support
* works on any Raspberry Pi model
* reads mono and stereo files
* reads data from stdin
* based on threads
Included sample audio was created by [graham_makes](https://freesound.org/people/graham_makes/sounds/449409/) and published on [freesound.org](https://freesound.org/)

Wyświetl plik

@ -1,7 +1,7 @@
/*
fm_transmitter - use Raspberry Pi as FM transmitter
Copyright (c) 2018, Marcin Kondej
Copyright (c) 2019, Marcin Kondej
All rights reserved.
See https://github.com/markondej/fm_transmitter
@ -42,7 +42,7 @@ ErrorReporter::~ErrorReporter() throw()
{
}
const char* ErrorReporter::what() const throw()
const char *ErrorReporter::what() const throw()
{
return errorMessage.c_str();
}

Wyświetl plik

@ -1,7 +1,7 @@
/*
fm_transmitter - use Raspberry Pi as FM transmitter
Copyright (c) 2018, Marcin Kondej
Copyright (c) 2019, Marcin Kondej
All rights reserved.
See https://github.com/markondej/fm_transmitter
@ -46,7 +46,7 @@ class ErrorReporter : public exception
explicit ErrorReporter(string message);
virtual ~ErrorReporter() throw();
virtual const char* what() const throw();
virtual const char *what() const throw();
protected:
string errorMessage;
};

262
mailbox.c 100644
Wyświetl plik

@ -0,0 +1,262 @@
/*
Copyright (c) 2012, Broadcom Europe Ltd.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* 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.
* 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 <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include "mailbox.h"
#define PAGE_SIZE (4*1024)
void *mapmem(unsigned base, unsigned size)
{
int mem_fd;
unsigned offset = base % PAGE_SIZE;
base = base - offset;
size = size + offset;
/* open /dev/mem */
if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) {
printf("can't open /dev/mem\nThis program should be run as root. Try prefixing command with: sudo\n");
exit (-1);
}
void *mem = mmap(
0,
size,
PROT_READ|PROT_WRITE,
MAP_SHARED/*|MAP_FIXED*/,
mem_fd,
base);
#ifdef DEBUG
printf("base=0x%x, mem=%p\n", base, mem);
#endif
if (mem == MAP_FAILED) {
printf("mmap error %d\n", (int)mem);
exit (-1);
}
close(mem_fd);
return (char *)mem + offset;
}
void unmapmem(void *addr, unsigned size)
{
const intptr_t offset = (intptr_t)addr % PAGE_SIZE;
addr = (char *)addr - offset;
size = size + offset;
int s = munmap(addr, size);
if (s != 0) {
printf("munmap error %d\n", s);
exit (-1);
}
}
/*
* use ioctl to send mbox property message
*/
static int mbox_property(int file_desc, void *buf)
{
int ret_val = ioctl(file_desc, IOCTL_MBOX_PROPERTY, buf);
if (ret_val < 0) {
printf("ioctl_set_msg failed:%d\n", ret_val);
}
#ifdef DEBUG
unsigned *p = buf; int i; unsigned size = *(unsigned *)buf;
for (i=0; i<size/4; i++)
printf("%04x: 0x%08x\n", i*sizeof *p, p[i]);
#endif
return ret_val;
}
unsigned mem_alloc(int file_desc, unsigned size, unsigned align, unsigned flags)
{
int i=0;
unsigned p[32];
p[i++] = 0; // size
p[i++] = 0x00000000; // process request
p[i++] = 0x3000c; // (the tag id)
p[i++] = 12; // (size of the buffer)
p[i++] = 12; // (size of the data)
p[i++] = size; // (num bytes? or pages?)
p[i++] = align; // (alignment)
p[i++] = flags; // (MEM_FLAG_L1_NONALLOCATING)
p[i++] = 0x00000000; // end tag
p[0] = i*sizeof *p; // actual size
mbox_property(file_desc, p);
return p[5];
}
unsigned mem_free(int file_desc, unsigned handle)
{
int i=0;
unsigned p[32];
p[i++] = 0; // size
p[i++] = 0x00000000; // process request
p[i++] = 0x3000f; // (the tag id)
p[i++] = 4; // (size of the buffer)
p[i++] = 4; // (size of the data)
p[i++] = handle;
p[i++] = 0x00000000; // end tag
p[0] = i*sizeof *p; // actual size
mbox_property(file_desc, p);
return p[5];
}
unsigned mem_lock(int file_desc, unsigned handle)
{
int i=0;
unsigned p[32];
p[i++] = 0; // size
p[i++] = 0x00000000; // process request
p[i++] = 0x3000d; // (the tag id)
p[i++] = 4; // (size of the buffer)
p[i++] = 4; // (size of the data)
p[i++] = handle;
p[i++] = 0x00000000; // end tag
p[0] = i*sizeof *p; // actual size
mbox_property(file_desc, p);
return p[5];
}
unsigned mem_unlock(int file_desc, unsigned handle)
{
int i=0;
unsigned p[32];
p[i++] = 0; // size
p[i++] = 0x00000000; // process request
p[i++] = 0x3000e; // (the tag id)
p[i++] = 4; // (size of the buffer)
p[i++] = 4; // (size of the data)
p[i++] = handle;
p[i++] = 0x00000000; // end tag
p[0] = i*sizeof *p; // actual size
mbox_property(file_desc, p);
return p[5];
}
unsigned execute_code(int file_desc, unsigned code, unsigned r0, unsigned r1, unsigned r2, unsigned r3, unsigned r4, unsigned r5)
{
int i=0;
unsigned p[32];
p[i++] = 0; // size
p[i++] = 0x00000000; // process request
p[i++] = 0x30010; // (the tag id)
p[i++] = 28; // (size of the buffer)
p[i++] = 28; // (size of the data)
p[i++] = code;
p[i++] = r0;
p[i++] = r1;
p[i++] = r2;
p[i++] = r3;
p[i++] = r4;
p[i++] = r5;
p[i++] = 0x00000000; // end tag
p[0] = i*sizeof *p; // actual size
mbox_property(file_desc, p);
return p[5];
}
unsigned qpu_enable(int file_desc, unsigned enable)
{
int i=0;
unsigned p[32];
p[i++] = 0; // size
p[i++] = 0x00000000; // process request
p[i++] = 0x30012; // (the tag id)
p[i++] = 4; // (size of the buffer)
p[i++] = 4; // (size of the data)
p[i++] = enable;
p[i++] = 0x00000000; // end tag
p[0] = i*sizeof *p; // actual size
mbox_property(file_desc, p);
return p[5];
}
unsigned execute_qpu(int file_desc, unsigned num_qpus, unsigned control, unsigned noflush, unsigned timeout) {
int i=0;
unsigned p[32];
p[i++] = 0; // size
p[i++] = 0x00000000; // process request
p[i++] = 0x30011; // (the tag id)
p[i++] = 16; // (size of the buffer)
p[i++] = 16; // (size of the data)
p[i++] = num_qpus;
p[i++] = control;
p[i++] = noflush;
p[i++] = timeout; // ms
p[i++] = 0x00000000; // end tag
p[0] = i*sizeof *p; // actual size
mbox_property(file_desc, p);
return p[5];
}
int mbox_open() {
int file_desc;
// open a char device file used for communicating with kernel mbox driver
file_desc = open(DEVICE_FILE_NAME, 0);
if (file_desc < 0) {
printf("Can't open device file: %s\n", DEVICE_FILE_NAME);
printf("Try creating a device file with: sudo mknod %s c %d 0\n", DEVICE_FILE_NAME, MAJOR_NUM);
exit(-1);
}
return file_desc;
}
void mbox_close(int file_desc) {
close(file_desc);
}

47
mailbox.h 100644
Wyświetl plik

@ -0,0 +1,47 @@
/*
Copyright (c) 2012, Broadcom Europe Ltd.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* 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.
* 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 <linux/ioctl.h>
#define MAJOR_NUM 100
#define IOCTL_MBOX_PROPERTY _IOWR(MAJOR_NUM, 0, char *)
#define DEVICE_FILE_NAME "/dev/vcio"
int mbox_open();
void mbox_close(int file_desc);
unsigned get_version(int file_desc);
unsigned mem_alloc(int file_desc, unsigned size, unsigned align, unsigned flags);
unsigned mem_free(int file_desc, unsigned handle);
unsigned mem_lock(int file_desc, unsigned handle);
unsigned mem_unlock(int file_desc, unsigned handle);
void *mapmem(unsigned base, unsigned size);
void unmapmem(void *addr, unsigned size);
unsigned execute_code(int file_desc, unsigned code, unsigned r0, unsigned r1, unsigned r2, unsigned r3, unsigned r4, unsigned r5);
unsigned execute_qpu(int file_desc, unsigned num_qpus, unsigned control, unsigned noflush, unsigned timeout);
unsigned qpu_enable(int file_desc, unsigned enable);

209
main.cpp
Wyświetl plik

@ -1,98 +1,111 @@
/*
fm_transmitter - use Raspberry Pi as FM transmitter
Copyright (c) 2018, 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 "transmitter.h"
#include <cstdlib>
#include <csignal>
#include <iostream>
#include <unistd.h>
using namespace std;
bool stop = false;
Transmitter* transmitter = NULL;
void sigIntHandler(int sigNum)
{
if (transmitter != NULL) {
cout << "Stopping..." << endl;
transmitter->stop();
stop = true;
}
}
int main(int argc, char** argv)
{
double frequency = 100.0;
bool loop = false;
string filename;
bool showUsage = true;
int opt;
while ((opt = getopt(argc, argv, "rf:")) != -1) {
switch (opt) {
case 'r':
loop = true;
break;
case 'f':
frequency = ::atof(optarg);
break;
}
}
if (optind < argc) {
filename = argv[optind];
showUsage = false;
}
if (showUsage) {
cout << "Usage: " << argv[0] << " [-f frequency] [-r] FILE" << endl;
return 0;
}
signal(SIGINT, sigIntHandler);
try {
transmitter = Transmitter::getInstance();
WaveReader reader(filename != "-" ? filename : string(), stop);
PCMWaveHeader header = reader.getHeader();
cout << "Playing: " << reader.getFilename() << ", "
<< header.sampleRate << " Hz, "
<< header.bitsPerSample << " bits, "
<< ((header.channels > 0x01) ? "stereo" : "mono") << endl;
transmitter->play(&reader, frequency, 0, loop);
} catch (exception &error) {
cout << "Error: " << error.what() << endl;
return 1;
}
return 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.
*/
#include "transmitter.h"
#include <cstdlib>
#include <csignal>
#include <iostream>
#include <unistd.h>
using namespace std;
bool play = true;
Transmitter *transmitter = NULL;
void sigIntHandler(int sigNum)
{
if (transmitter != NULL) {
cout << "Stopping..." << endl;
transmitter->stop();
play = false;
}
}
int main(int argc, char** argv)
{
double frequency = 100.0;
unsigned short dmaChannel = 0;
bool loop = false;
string filename;
bool showUsage = true;
int opt, filesOffset;
while ((opt = getopt(argc, argv, "rf:d:v")) != -1) {
switch (opt) {
case 'r':
loop = true;
break;
case 'f':
frequency = ::atof(optarg);
break;
case 'd':
dmaChannel = ::atof(optarg);
break;
case 'v':
cout << EXECUTABLE << " version: " << VERSION << endl;
return 0;
}
}
if (optind < argc) {
filesOffset = optind;
showUsage = false;
}
if (showUsage) {
cout << "Usage: " << EXECUTABLE << " [-f <frequency>] [-d <dma_channel>] [-r] <file>" << endl;
return 0;
}
signal(SIGINT, sigIntHandler);
try {
transmitter = &Transmitter::getInstance();
do {
filename = argv[optind++];
if ((optind == argc) && loop) {
optind = filesOffset;
}
WaveReader reader(filename != "-" ? filename : string(), play);
PCMWaveHeader header = reader.getHeader();
cout << "Playing: " << reader.getFilename() << ", "
<< header.sampleRate << " Hz, "
<< header.bitsPerSample << " bits, "
<< ((header.channels > 0x01) ? "stereo" : "mono") << endl;
transmitter->play(reader, frequency, dmaChannel, optind < argc);
} while (play && (optind < argc));
} catch (exception &error) {
cout << "Error: " << error.what() << endl;
return 1;
}
return 0;
}

Wyświetl plik

@ -1,24 +1,32 @@
EXECUTABLE = fm_transmitter
VERSION = 0.9.1
FLAGS = -Wall -fexceptions -pthread -O3 -fpermissive -fno-strict-aliasing
INCLUDES = -I/opt/vc/include -L/opt/vc/lib
LIBS = -lm -lbcm_host
TARGET = fm_transmitter
LIBS = -lm
CPP=$(CCPREFIX)g++
all: main.o error_reporter.o wave_reader.o transmitter.o
$(CPP) $(FLAGS) $(INCLUDES) $(LIBS) -o $(TARGET) main.o error_reporter.o wave_reader.o transmitter.o
wave_reader.o: wave_reader.cpp wave_reader.h
$(CPP) $(FLAGS) $(INCLUDES) $(LIBS) -c wave_reader.cpp
all: main.o mailbox.o error_reporter.o sample.o preemp.o wave_reader.o transmitter.o
g++ $(FLAGS) -L/opt/vc/lib -lm -lbcm_host -o $(EXECUTABLE) main.o mailbox.o sample.o preemp.o error_reporter.o wave_reader.o transmitter.o
mailbox.o: mailbox.c mailbox.h
g++ $(FLAGS) -c mailbox.c
error_reporter.o: error_reporter.cpp error_reporter.h
$(CPP) $(FLAGS) $(INCLUDES) $(LIBS) -c error_reporter.cpp
g++ $(FLAGS) -c error_reporter.cpp
sample.o: sample.cpp sample.h
g++ $(FLAGS) -c sample.cpp
preemp.o: preemp.cpp preemp.h
g++ $(FLAGS) -c preemp.cpp
wave_reader.o: wave_reader.cpp wave_reader.h
g++ $(FLAGS) -c wave_reader.cpp
transmitter.o: transmitter.cpp transmitter.h
$(CPP) $(FLAGS) $(INCLUDES) $(LIBS) -c transmitter.cpp
g++ $(FLAGS) -I/opt/vc/include -c transmitter.cpp
main.o: main.cpp
$(CPP) $(FLAGS) $(INCLUDES) $(LIBS) -c main.cpp
g++ $(FLAGS) -DVERSION=\"$(VERSION)\" -DEXECUTABLE=\"$(EXECUTABLE)\" -c main.cpp
clean:
rm *.o

Wyświetl plik

@ -1,7 +1,7 @@
/*
fm_transmitter - use Raspberry Pi as FM transmitter
Copyright (c) 2018, Marcin Kondej
Copyright (c) 2019, Marcin Kondej
All rights reserved.
See https://github.com/markondej/fm_transmitter

59
preemp.cpp 100644
Wyświetl plik

@ -0,0 +1,59 @@
/*
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.
*/
#include "preemp.h"
PreEmp::PreEmp(unsigned sampleRate)
: ratio(0.75 - 250000.0 / (float)(sampleRate * 75)), prevValue(0.0)
{
}
PreEmp::PreEmp(const PreEmp &source)
: ratio(source.ratio), prevValue(source.prevValue)
{
}
PreEmp &PreEmp::operator=(const PreEmp &source)
{
ratio = source.ratio;
prevValue = source.prevValue;
return *this;
}
float PreEmp::filter(float value)
{
value = value + (value - prevValue) * ratio;
value = (value < -1.0) ? -1.0 : ((value > 1.0) ? 1.0 : value);
prevValue = value;
return value;
}

48
preemp.h 100644
Wyświetl plik

@ -0,0 +1,48 @@
/*
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 PREEMP_H
#define PREEMP_H
class PreEmp
{
public:
PreEmp(unsigned sampleRate);
PreEmp(const PreEmp &source);
PreEmp &operator=(const PreEmp &source);
float filter(float value);
protected:
float ratio, prevValue;
};
#endif // PREEMP_H

68
sample.cpp 100644
Wyświetl plik

@ -0,0 +1,68 @@
/*
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.
*/
#include "sample.h"
Sample::Sample(char *data, unsigned short channels, unsigned short bitsPerChannel)
: value(0)
{
int sum = 0;
short *channelValues = new short[channels];
short multiplier = bitsPerChannel >> 3;
for (unsigned i = 0; i < channels; i++) {
if (multiplier > 1) {
channelValues[i] = (data[(i + 1) * multiplier - 1] << 8) | data[(i + 1) * multiplier - 2];
} else {
channelValues[i] = ((short)(unsigned char)data[i] - 0x80) << 8;
}
sum += channelValues[i];
}
value = sum / channels;
delete[] channelValues;
}
Sample::Sample(const Sample &source)
: value(source.value)
{
}
Sample &Sample::operator=(const Sample &source)
{
value = source.value;
return *this;
}
float Sample::getMonoValue()
{
return (short)(value & 0xFF00) / 32768.0;
}

48
sample.h 100644
Wyświetl plik

@ -0,0 +1,48 @@
/*
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 SAMPLE_H
#define SAMPLE_H
class Sample
{
public:
Sample(char *data, unsigned short channels, unsigned short bitsPerChannel);
Sample(const Sample &source);
Sample &operator=(const Sample &source);
float getMonoValue();
protected:
short value;
};
#endif // SAMPLE_H

Wyświetl plik

@ -1,7 +1,7 @@
/*
fm_transmitter - use Raspberry Pi as FM transmitter
Copyright (c) 2018, Marcin Kondej
Copyright (c) 2019, Marcin Kondej
All rights reserved.
See https://github.com/markondej/fm_transmitter
@ -32,7 +32,9 @@
*/
#include "transmitter.h"
#include "preemp.h"
#include "error_reporter.h"
#include "mailbox.h"
#include <bcm_host.h>
#include <sstream>
#include <cmath>
@ -42,18 +44,77 @@
using std::ostringstream;
using std::vector;
#define GPIO_BASE 0x00200000
#define CLK0_BASE 0x00101070
#define CLK0DIV_BASE 0x00101074
#define TCNT_BASE 0x00003004
#define PERIPHERALS_PHYS_BASE 0x7E000000
#define DMA0_BASE_OFFSET 0x00007000
#define DMA15_BASE_OFFSET 0x00E05000
#define CLK0_BASE_OFFSET 0x00101070
#define PWMCLK_BASE_OFFSET 0x001010A0
#define GPIO_BASE_OFFSET 0x00200000
#define PWM_BASE_OFFSET 0x0020C000
#define TIMER_BASE_OFFSET 0x00003000
#define ACCESS(base, offset) *(volatile unsigned*)((int)base + offset)
#define ACCESS64(base, offset) *(volatile unsigned long long*)((int)base + offset)
#define BUFFER_TIME 1000000
#define PWM_CLOCK_FREQUENCY 200
#define PWM_CHANNEL_RANGE 32
#define TIMING_FACTOR 1.237
#define PAGE_SIZE 4096
struct TimerRegisters {
unsigned ctlStatus;
unsigned low;
unsigned high;
unsigned c0;
unsigned c1;
unsigned c2;
unsigned c3;
};
struct ClockRegisters {
unsigned ctl;
unsigned div;
};
struct PWMRegisters {
unsigned ctl;
unsigned status;
unsigned dmaConf;
unsigned chn1Range;
unsigned chn1Data;
unsigned fifoIn;
unsigned chn2Range;
unsigned chn2Data;
};
struct DMAControllBlock {
unsigned transferInfo;
unsigned srcAddress;
unsigned dstAddress;
unsigned transferLen;
unsigned stride;
unsigned nextCB;
unsigned reserved0;
unsigned reserved1;
};
struct DMARegisters {
unsigned ctlStatus;
unsigned cbAddress;
unsigned transferInfo;
unsigned srcAddress;
unsigned dstAddress;
unsigned transferLen;
unsigned stride;
unsigned nextCB;
unsigned debug;
};
bool Transmitter::transmitting = false;
void* Transmitter::peripherals = NULL;
bool Transmitter::clockInitialized = false;
bool Transmitter::preserveCarrier = false;
void *Transmitter::peripherals = NULL;
Transmitter::Transmitter() : forceStop(false)
Transmitter::Transmitter()
{
int memFd;
if ((memFd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) {
@ -72,184 +133,311 @@ Transmitter::~Transmitter()
munmap(peripherals, bcm_host_get_peripheral_size());
}
Transmitter* Transmitter::getInstance()
Transmitter &Transmitter::getInstance()
{
static Transmitter instance;
return &instance;
return instance;
}
void Transmitter::play(WaveReader* reader, double frequency, unsigned char dmaChannel, bool loop)
void Transmitter::play(WaveReader &reader, double frequency, unsigned char dmaChannel, bool preserveCarrierOnExit)
{
if (transmitting) {
throw ErrorReporter("Cannot play, transmitter already in use");
}
transmitting = true;
forceStop = false;
preserveCarrier = preserveCarrierOnExit;
PCMWaveHeader header = reader->getHeader();
unsigned bufferFrames = (unsigned)((unsigned long long)header.sampleRate * BUFFER_TIME / 1000000);
vector<float>* frames = reader->getFrames(bufferFrames, forceStop);
if (frames == NULL) {
PCMWaveHeader header = reader.getHeader();
unsigned bufferSize = (unsigned)((unsigned long long)header.sampleRate * BUFFER_TIME / 1000000);
vector<Sample> *samples = reader.getSamples(bufferSize, transmitting);
if (samples == NULL) {
return;
}
bool eof = frames->size() < bufferFrames;
vector<float>* buffer = frames;
bool eof = samples->size() < bufferSize;
unsigned frameOffset = 0;
unsigned clockDivisor = (unsigned)((500 << 12) / frequency + 0.5);
bool restart = false;
void* transmitterParams[6] = {
(void*)&restart,
(void*)&buffer,
(void*)&frameOffset,
(void*)&clockDivisor,
(void*)&header.sampleRate,
(void*)&dmaChannel
};
if (dmaChannel != 0xFF) {
if (dmaChannel > 15) {
delete samples;
throw ErrorReporter("DMA channel number out of range(0 - 15)");
}
pthread_t thread;
int returnCode = pthread_create(&thread, NULL, &Transmitter::transmit, (void*)transmitterParams);
if (returnCode) {
delete frames;
ostringstream oss;
oss << "Cannot create new thread (code: " << returnCode << ")";
throw ErrorReporter(oss.str());
}
int mbFd = mbox_open();
unsigned reqMemSize = sizeof(unsigned) * (bufferSize + 1) + sizeof(DMAControllBlock) * (bufferSize << 1);
if (reqMemSize % PAGE_SIZE) {
reqMemSize = (reqMemSize / PAGE_SIZE + 1) * PAGE_SIZE;
}
unsigned memHandle = mem_alloc(mbFd, reqMemSize, PAGE_SIZE, (bcm_host_get_peripheral_address() == 0x20000000) ? 0x0C : 0x04);
if (!memHandle) {
delete samples;
throw ErrorReporter("Cannot allocate memory");
}
unsigned physAddr = mem_lock(mbFd, memHandle);
void *virtAddr = mapmem(physAddr & ~0xC0000000, reqMemSize);
usleep(BUFFER_TIME / 2);
ClockRegisters *clk0 = (ClockRegisters *)getPeripheral(CLK0_BASE_OFFSET);
unsigned *gpio = (unsigned *)getPeripheral(GPIO_BASE_OFFSET);
if (!clockInitialized) {
clk0->ctl = (0x5A << 24) | 0x06;
usleep(1000);
clk0->div = (0x5A << 24) | clockDivisor;
clk0->ctl = (0x5A << 24) | (0x01 << 9) | (0x01 << 4) | 0x06;
*gpio = (*(volatile unsigned *)gpio & 0xFFFF8FFF) | (0x01 << 14);
clockInitialized = true;
}
bool isError = false;
string errorMessage;
ClockRegisters *pwmClk = (ClockRegisters *)getPeripheral(PWMCLK_BASE_OFFSET);
pwmClk->ctl = (0x5A << 24) | 0x06;
usleep(1000);
pwmClk->div = (0x5A << 24) | ((500 << 12) / PWM_CLOCK_FREQUENCY);
pwmClk->ctl = (0x5A << 24) | (0x01 << 4) | 0x06;
try {
while (!forceStop) {
while (!eof && !forceStop) {
if (buffer == NULL) {
if (!reader->setFrameOffset(frameOffset + bufferFrames)) {
break;
}
frames = reader->getFrames(bufferFrames, forceStop);
if (frames == NULL) {
forceStop = true;
break;
}
eof = frames->size() < bufferFrames;
buffer = frames;
PWMRegisters *pwm = (PWMRegisters *)getPeripheral(PWM_BASE_OFFSET);
pwm->ctl = 0x00;
usleep(1000);
pwm->status = 0x01FC;
pwm->ctl = (0x01 << 6);
usleep(1000);
pwm->chn1Range = PWM_CHANNEL_RANGE;
pwm->dmaConf = (0x01 << 31) | 0x0707;
pwm->ctl = (0x01 << 5) | (0x01 << 2) | 0x01;
#ifndef NO_PREEMP
PreEmp preEmp(header.sampleRate);
#endif
float value;
unsigned i;
DMAControllBlock *dmaCb = (DMAControllBlock *)(unsigned *)virtAddr;
unsigned *clkDiv = (unsigned *)virtAddr + ((sizeof(DMAControllBlock) / sizeof(unsigned)) << 1) * bufferSize;
unsigned *pwmFifoData = (unsigned *)virtAddr + (((sizeof(DMAControllBlock) / sizeof(unsigned)) << 1) + 1) * bufferSize;
unsigned pwmWrites = (unsigned)((PWM_CLOCK_FREQUENCY * 1000000.0 / PWM_CHANNEL_RANGE / header.sampleRate) * TIMING_FACTOR);
for (i = 0; i < bufferSize; i++) {
value = (*samples)[i].getMonoValue();
#ifndef NO_PREEMP
value = preEmp.filter(value);
#endif
clkDiv[i] = (0x5A << 24) | ((clockDivisor) - (int)(round(value * 16.0)));
dmaCb[i << 1].transferInfo = (0x01 << 26) | (0x01 << 3);
dmaCb[i << 1].srcAddress = physAddr + ((unsigned)&clkDiv[i] - (unsigned)virtAddr);
dmaCb[i << 1].dstAddress = PERIPHERALS_PHYS_BASE | (CLK0_BASE_OFFSET + 0x04);
dmaCb[i << 1].transferLen = sizeof(unsigned);
dmaCb[i << 1].stride = 0;
dmaCb[i << 1].nextCB = physAddr + ((unsigned)&dmaCb[(i << 1) + 1] - (unsigned)virtAddr);
dmaCb[(i << 1) + 1].transferInfo = (0x01 << 26) | (0x05 << 16) | (0x01 << 6) | (0x01 << 3);
dmaCb[(i << 1) + 1].srcAddress = physAddr + ((unsigned)pwmFifoData - (unsigned)virtAddr);
dmaCb[(i << 1) + 1].dstAddress = PERIPHERALS_PHYS_BASE | (PWM_BASE_OFFSET + 0x18);
dmaCb[(i << 1) + 1].transferLen = sizeof(unsigned) * pwmWrites;
dmaCb[(i << 1) + 1].stride = 0;
dmaCb[(i << 1) + 1].nextCB = physAddr + ((unsigned)((i < bufferSize - 1) ? &dmaCb[(i << 1) + 2] : dmaCb) - (unsigned)virtAddr);
}
*pwmFifoData = 0x00;
delete samples;
DMARegisters *dma = (DMARegisters *)getPeripheral((dmaChannel < 15) ? DMA0_BASE_OFFSET + dmaChannel * 0x100 : DMA15_BASE_OFFSET);
dma->ctlStatus = (0x01 << 31);
usleep(1000);
dma->ctlStatus = (0x01 << 2) | (0x01 << 1);
dma->cbAddress = physAddr + ((unsigned)dmaCb - (unsigned)virtAddr);
dma->ctlStatus = (0xFF << 16) | 0x01;
usleep(BUFFER_TIME / 2);
bool isError = false;
string errorMessage;
try {
while (!eof && transmitting) {
samples = reader.getSamples(bufferSize, transmitting);
if (samples == NULL) {
break;
}
usleep(BUFFER_TIME / 2);
eof = samples->size() < bufferSize;
for (i = 0; i < samples->size(); i++) {
value = (*samples)[i].getMonoValue();
#ifndef NO_PREEMP
value = preEmp.filter(value);
#endif
while (i == (((dma->cbAddress - physAddr - ((unsigned)dmaCb - (unsigned)virtAddr)) / sizeof(DMAControllBlock)) >> 1)) {
usleep(1);
}
clkDiv[i] = (0x5A << 24) | ((clockDivisor)-(int)(round(value * 16.0)));
}
delete samples;
}
if (loop && !forceStop) {
frameOffset = 0;
restart = true;
if (!reader->setFrameOffset(0)) {
break;
} catch (ErrorReporter &error) {
preserveCarrier = false;
errorMessage = error.what();
isError = true;
}
if (eof) {
dmaCb[i << 1].nextCB = 0x00;
} else {
dmaCb[(bufferSize - 1) << 1].nextCB = 0x00;
}
while (dma->cbAddress != 0x00) {
usleep(1);
}
dma->ctlStatus = (0x01 << 31);
pwm->ctl = 0x00;
unmapmem(virtAddr, reqMemSize);
mem_unlock(mbFd, memHandle);
mem_free(mbFd, memHandle);
mbox_close(mbFd);
transmitting = false;
if (!preserveCarrier) {
clk0->ctl = (0x5A << 24) | 0x06;
}
if (isError) {
throw ErrorReporter(errorMessage);
}
} else {
unsigned sampleOffset = 0;
vector<Sample> *buffer = samples;
void *transmitterParams[4] = {
(void *)&buffer,
(void *)&sampleOffset,
(void *)&clockDivisor,
(void *)&header.sampleRate
};
pthread_t thread;
int returnCode = pthread_create(&thread, NULL, &Transmitter::transmit, (void *)transmitterParams);
if (returnCode) {
delete samples;
ostringstream oss;
oss << "Cannot create transmitter thread (code: " << returnCode << ")";
throw ErrorReporter(oss.str());
}
usleep(BUFFER_TIME / 2);
bool isError = false;
string errorMessage;
try {
while (!eof && transmitting) {
if (buffer == NULL) {
if (!reader.setSampleOffset(sampleOffset + bufferSize)) {
break;
}
samples = reader.getSamples(bufferSize, transmitting);
if (samples == NULL) {
break;
}
eof = samples->size() < bufferSize;
buffer = samples;
}
frames = reader->getFrames(bufferFrames, forceStop);
if (frames == NULL) {
break;
}
eof = frames->size() < bufferFrames;
buffer = frames;
usleep(BUFFER_TIME / 2);
} else {
forceStop = true;
}
}
} catch (ErrorReporter &error) {
errorMessage = error.what();
isError = true;
}
transmitting = false;
pthread_join(thread, NULL);
if (isError) {
throw ErrorReporter(errorMessage);
catch (ErrorReporter &error) {
preserveCarrier = false;
errorMessage = error.what();
isError = true;
}
transmitting = false;
pthread_join(thread, NULL);
if (isError) {
throw ErrorReporter(errorMessage);
}
}
}
void* Transmitter::transmit(void* params)
{
bool* restart = (bool*)((void**)params)[0];
vector<float>** buffer = (vector<float>**)((void**)params)[1];
unsigned* frameOffset = (unsigned*)((void**)params)[2];
unsigned clockDivisor = *(unsigned*)((void**)params)[3], sampleRate = *(unsigned*)((void**)params)[4];
unsigned char dmaChannel = *(unsigned char*)((void**)params)[5];
volatile void *Transmitter::getPeripheral(unsigned offset) {
return (volatile void *)((unsigned)peripherals + offset);
}
void *Transmitter::transmit(void *params)
{
vector<Sample> **buffer = (vector<Sample> **)((void **)params)[0];
unsigned *sampleOffset = (unsigned *)((void **)params)[1];
unsigned *clockDivisor = (unsigned *)((void **)params)[2];
unsigned *sampleRate = (unsigned *)((void **)params)[3];
unsigned long long current, start, playbackStart;
unsigned offset, length, prevOffset;
vector<float>* frames = NULL;
float* data;
vector<Sample> *samples = NULL;
unsigned long long start;
float value;
#ifndef NO_PREEMP
float prevValue = 0.0;
float preemp = 0.75 - 250000.0 / (float)(sampleRate * 75);
PreEmp preEmp(*sampleRate);
#endif
ACCESS(peripherals, GPIO_BASE) = (ACCESS(peripherals, GPIO_BASE) & 0xFFFF8FFF) | (0x01 << 14);
ACCESS(peripherals, CLK0_BASE) = (0x5A << 24) | (0x01 << 9) | (0x01 << 4) | 0x06;
ClockRegisters *clk0 = (ClockRegisters *)getPeripheral(CLK0_BASE_OFFSET);
unsigned *gpio = (unsigned *)getPeripheral(GPIO_BASE_OFFSET);
if (!clockInitialized) {
clk0->ctl = (0x5A << 24) | 0x06;
usleep(1000);
clk0->div = (0x5A << 24) | *clockDivisor;
clk0->ctl = (0x5A << 24) | (0x01 << 9) | (0x01 << 4) | 0x06;
*gpio = (*gpio & 0xFFFF8FFF) | (0x01 << 14);
clockInitialized = true;
}
current = ACCESS64(peripherals, TCNT_BASE);
playbackStart = current;
TimerRegisters *timer = (TimerRegisters *)getPeripheral(TIMER_BASE_OFFSET);
unsigned long long current = *(volatile unsigned long long *)&timer->low;
unsigned long long playbackStart = current;
while (transmitting) {
start = current;
while ((*buffer == NULL) && transmitting) {
usleep(1);
current = ACCESS64(peripherals, TCNT_BASE);
current = *(volatile unsigned long long *)&timer->low;
}
if (!transmitting) {
break;
}
if (*restart) {
playbackStart = current;
start = current;
*restart = false;
}
frames = *buffer;
*frameOffset = (current - playbackStart) * (sampleRate) / 1000000;
samples = *buffer;
length = samples->size();
*buffer = NULL;
offset = (current - start) * (sampleRate) / 1000000;
length = frames->size();
data = &(*frames)[0];
*sampleOffset = (current - playbackStart) * (*sampleRate) / 1000000;
offset = (current - start) * (*sampleRate) / 1000000;
while (true) {
if (offset >= length) {
break;
}
prevOffset = offset;
value = data[offset];
value = (*samples)[offset].getMonoValue();
#ifndef NO_PREEMP
value = value + (value - prevValue) * preemp;
value = (value < -1.0) ? -1.0 : ((value > 1.0) ? 1.0 : value);
value = preEmp.filter(value);
#endif
ACCESS(peripherals, CLK0DIV_BASE) = (0x5A << 24) | ((clockDivisor) - (int)(round(value * 16.0)));
clk0->div = (0x5A << 24) | ((*clockDivisor) - (int)(round(value * 16.0)));
while (offset == prevOffset) {
asm("nop");
current = ACCESS64(peripherals, TCNT_BASE);
offset = (current - start) * (sampleRate) / 1000000;
usleep(1); // asm("nop");
current = *(volatile unsigned long long *)&timer->low;
offset = (current - start) * (*sampleRate) / 1000000;
}
#ifndef NO_PREEMP
prevValue = value;
#endif
}
delete frames;
delete samples;
}
ACCESS(peripherals, CLK0_BASE) = (0x5A << 24);
if (!preserveCarrier) {
clk0->ctl = (0x5A << 24) | 0x06;
}
return NULL;
}
void Transmitter::stop()
{
forceStop = true;
preserveCarrier = false;
transmitting = false;
}

Wyświetl plik

@ -1,7 +1,7 @@
/*
fm_transmitter - use Raspberry Pi as FM transmitter
Copyright (c) 2018, Marcin Kondej
Copyright (c) 2019, Marcin Kondej
All rights reserved.
See https://github.com/markondej/fm_transmitter
@ -36,24 +36,24 @@
#include "wave_reader.h"
#define BUFFER_TIME 1000000
using std::string;
class Transmitter
{
public:
virtual ~Transmitter();
static Transmitter* getInstance();
void play(WaveReader* reader, double frequency, unsigned char dmaChannel, bool loop);
static Transmitter &getInstance();
void play(WaveReader &reader, double frequency, unsigned char dmaChannel, bool preserveCarrierOnExit);
void stop();
private:
Transmitter();
static void* transmit(void* params);
Transmitter(const Transmitter &source);
Transmitter &operator=(const Transmitter &source);
static volatile void *getPeripheral(unsigned offset);
static void *transmit(void *params);
static void* peripherals;
static bool transmitting;
bool forceStop;
static void *peripherals;
static bool transmitting, clockInitialized, preserveCarrier;
};
#endif // TRANSMITTER_H

Wyświetl plik

@ -1,7 +1,7 @@
/*
fm_transmitter - use Raspberry Pi as FM transmitter
Copyright (c) 2018, Marcin Kondej
Copyright (c) 2019, Marcin Kondej
All rights reserved.
See https://github.com/markondej/fm_transmitter
@ -41,10 +41,9 @@
using std::ostringstream;
using std::exception;
WaveReader::WaveReader(string filename, bool &forceStop) :
filename(filename), headerOffset(0), currentFrameOffset(0)
WaveReader::WaveReader(string filename, bool &continueFlag) :
filename(filename), headerOffset(0), currentDataOffset(0)
{
char* headerData = (char*)((void*)&header);
ostringstream oss;
if (!filename.empty()) {
@ -60,15 +59,13 @@ WaveReader::WaveReader(string filename, bool &forceStop) :
}
try {
readData(sizeof(PCMWaveHeader::chunkID) + sizeof(PCMWaveHeader::chunkSize) + sizeof(PCMWaveHeader::format), true, forceStop, headerData);
readData(sizeof(PCMWaveHeader::chunkID) + sizeof(PCMWaveHeader::chunkSize) + sizeof(PCMWaveHeader::format), true, continueFlag);
if ((string(header.chunkID, 4) != string("RIFF")) || (string(header.format, 4) != string("WAVE"))) {
oss << "Error while opening " << getFilename() << ", WAVE file expected";
throw ErrorReporter(oss.str());
}
readData(sizeof(PCMWaveHeader::subchunk1ID) + sizeof(PCMWaveHeader::subchunk1Size), true, forceStop, headerData);
readData(sizeof(PCMWaveHeader::subchunk1ID) + sizeof(PCMWaveHeader::subchunk1Size), true, continueFlag);
unsigned subchunk1MinSize = sizeof(PCMWaveHeader::audioFormat) + sizeof(PCMWaveHeader::channels) +
sizeof(PCMWaveHeader::sampleRate) + sizeof(PCMWaveHeader::byteRate) + sizeof(PCMWaveHeader::blockAlign) +
sizeof(PCMWaveHeader::bitsPerSample);
@ -77,8 +74,7 @@ WaveReader::WaveReader(string filename, bool &forceStop) :
throw ErrorReporter(oss.str());
}
readData(header.subchunk1Size, true, forceStop, headerData);
readData(header.subchunk1Size, true, continueFlag);
if ((header.audioFormat != WAVE_FORMAT_PCM) ||
(header.byteRate != (header.bitsPerSample >> 3) * header.channels * header.sampleRate) ||
(header.blockAlign != (header.bitsPerSample >> 3) * header.channels) ||
@ -87,8 +83,7 @@ WaveReader::WaveReader(string filename, bool &forceStop) :
throw ErrorReporter(oss.str());
}
readData(sizeof(PCMWaveHeader::subchunk2ID) + sizeof(PCMWaveHeader::subchunk2Size), true, forceStop, headerData);
readData(sizeof(PCMWaveHeader::subchunk2ID) + sizeof(PCMWaveHeader::subchunk2Size), true, continueFlag);
if (string(header.subchunk2ID, 4) != string("data")) {
oss << "Error while opening " << getFilename() << ", data corrupted";
throw ErrorReporter(oss.str());
@ -112,13 +107,13 @@ WaveReader::~WaveReader()
}
}
vector<char>* WaveReader::readData(unsigned bytesToRead, bool headerBytes, bool &forceStop, char* headerData)
vector<char> *WaveReader::readData(unsigned bytesToRead, bool headerBytes, bool &continueFlag)
{
unsigned bytesRead = 0;
vector<char>* data = new vector<char>();
vector<char> *data = new vector<char>();
data->resize(bytesToRead);
while ((bytesRead < bytesToRead) && !forceStop) {
while ((bytesRead < bytesToRead) && continueFlag) {
int bytes = read(fileDescriptor, &(*data)[bytesRead], bytesToRead - bytesRead);
if (((bytes == -1) && ((fileDescriptor != STDIN_FILENO) || (errno != EAGAIN))) ||
(((unsigned)bytes < bytesToRead) && headerBytes && (fileDescriptor != STDIN_FILENO))) {
@ -144,7 +139,7 @@ vector<char>* WaveReader::readData(unsigned bytesToRead, bool headerBytes, bool
}
}
if (forceStop) {
if (!continueFlag) {
delete data;
data = NULL;
}
@ -153,69 +148,56 @@ vector<char>* WaveReader::readData(unsigned bytesToRead, bool headerBytes, bool
if (data == NULL) {
throw ErrorReporter("Cannot obtain header, program interrupted");
}
memcpy(&headerData[headerOffset], &(*data)[0], bytesRead);
memcpy(&((char *)&header)[headerOffset], &(*data)[0], bytesRead);
headerOffset += bytesRead;
delete data;
data = NULL;
} else {
currentFrameOffset += bytesRead;
currentDataOffset += bytesRead;
}
return data;
}
vector<float>* WaveReader::getFrames(unsigned frameCount, bool &forceStop) {
unsigned bytesToRead, bytesLeft, bytesPerFrame, offset;
vector<float>* frames = new vector<float>();
vector<char>* data;
vector<Sample> *WaveReader::getSamples(unsigned quantity, bool &continueFlag) {
unsigned bytesToRead, bytesLeft, bytesPerSample;
vector<Sample> *samples = new vector<Sample>();
vector<char> *data;
bytesPerFrame = (header.bitsPerSample >> 3) * header.channels;
bytesToRead = frameCount * bytesPerFrame;
bytesLeft = header.subchunk2Size - currentFrameOffset;
bytesPerSample = (header.bitsPerSample >> 3) * header.channels;
bytesToRead = quantity * bytesPerSample;
bytesLeft = header.subchunk2Size - currentDataOffset;
if (bytesToRead > bytesLeft) {
bytesToRead = bytesLeft - bytesLeft % bytesPerFrame;
frameCount = bytesToRead / bytesPerFrame;
bytesToRead = bytesLeft - bytesLeft % bytesPerSample;
quantity = bytesToRead / bytesPerSample;
}
try {
data = readData(bytesToRead, false, forceStop, NULL);
data = readData(bytesToRead, false, continueFlag);
} catch (ErrorReporter &error) {
delete frames;
delete samples;
throw error;
}
if (data == NULL) {
delete frames;
delete samples;
return NULL;
}
if (data->size() < bytesToRead) {
frameCount = data->size() / bytesPerFrame;
quantity = data->size() / bytesPerSample;
}
for (unsigned i = 0; i < frameCount; i++) {
offset = bytesPerFrame * i;
if (header.channels != 1) {
if (header.bitsPerSample != 8) {
frames->push_back(((int)(signed char)(*data)[offset + 1] + (int)(signed char)(*data)[offset + 3]) / (float)0x100);
} else {
frames->push_back(((int)(*data)[offset] + (int)(*data)[offset + 1]) / (float)0x100 - 1.0f);
}
} else {
if (header.bitsPerSample != 8) {
frames->push_back((signed char)(*data)[offset + 1] / (float)0x80);
} else {
frames->push_back((*data)[offset] / (float)0x80 - 1.0f);
}
}
for (unsigned i = 0; i < quantity; i++) {
samples->push_back(Sample(&(*data)[bytesPerSample * i], header.channels, header.bitsPerSample));
}
delete data;
return frames;
return samples;
}
bool WaveReader::setFrameOffset(unsigned frameOffset) {
bool WaveReader::setSampleOffset(unsigned offset) {
if (fileDescriptor != STDIN_FILENO) {
currentFrameOffset = frameOffset * (header.bitsPerSample >> 3) * header.channels;
if (lseek(fileDescriptor, dataOffset + currentFrameOffset, SEEK_SET) == -1) {
currentDataOffset = offset * (header.bitsPerSample >> 3) * header.channels;
if (lseek(fileDescriptor, dataOffset + currentDataOffset, SEEK_SET) == -1) {
return false;
}
}

Wyświetl plik

@ -1,7 +1,7 @@
/*
fm_transmitter - use Raspberry Pi as FM transmitter
Copyright (c) 2018, Marcin Kondej
Copyright (c) 2019, Marcin Kondej
All rights reserved.
See https://github.com/markondej/fm_transmitter
@ -35,6 +35,7 @@
#define WAVE_READER_H
#include "pcm_wave_header.h"
#include "sample.h"
#include <string>
#include <vector>
@ -44,18 +45,20 @@ using std::string;
class WaveReader
{
public:
WaveReader(string filename, bool &forceStop);
WaveReader(string filename, bool &continueFlag);
virtual ~WaveReader();
string getFilename();
PCMWaveHeader getHeader();
vector<float>* getFrames(unsigned frameCount, bool &forceStop);
bool setFrameOffset(unsigned frameOffset);
vector<Sample> *getSamples(unsigned quantity, bool &continueFlag);
bool setSampleOffset(unsigned offset);
private:
vector<char>* readData(unsigned bytesToRead, bool headerBytes, bool &forceStop, char* headerData);
vector<char> *readData(unsigned bytesToRead, bool headerBytes, bool &continueFlag);
WaveReader(const WaveReader &source);
WaveReader &operator=(const WaveReader &source);
string filename;
PCMWaveHeader header;
unsigned dataOffset, headerOffset, currentFrameOffset;
unsigned dataOffset, headerOffset, currentDataOffset;
int fileDescriptor;
};