kopia lustrzana https://github.com/markondej/fm_transmitter
rodzic
ea843abcbc
commit
11fcc4c35b
14
README.md
14
README.md
|
@ -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/)
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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
209
main.cpp
|
@ -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;
|
||||
}
|
||||
|
|
34
makefile
34
makefile
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
430
transmitter.cpp
430
transmitter.cpp
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue