2015-07-22 15:38:41 +00:00
|
|
|
/*
|
|
|
|
fm_transmitter - use Raspberry Pi as FM transmitter
|
|
|
|
|
|
|
|
Copyright (c) 2015, 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:
|
2015-07-28 13:47:53 +00:00
|
|
|
|
2015-07-22 15:38:41 +00:00
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2015-03-30 23:20:45 +00:00
|
|
|
#include "transmitter.h"
|
2015-07-22 15:38:41 +00:00
|
|
|
#include <exception>
|
|
|
|
#include <sstream>
|
|
|
|
#include <cmath>
|
|
|
|
#include <string.h>
|
|
|
|
#include <unistd.h>
|
2015-03-30 23:20:45 +00:00
|
|
|
#include <sys/mman.h>
|
|
|
|
#include <fcntl.h>
|
2015-07-22 15:38:41 +00:00
|
|
|
|
|
|
|
using std::exception;
|
|
|
|
using std::ostringstream;
|
2015-03-30 23:20:45 +00:00
|
|
|
|
2015-04-03 13:34:53 +00:00
|
|
|
#define ACCESS(base, offset) *(volatile unsigned int*)((int)base + offset)
|
|
|
|
#define ACCESS64(base, offset) *(volatile unsigned long long*)((int)base + offset)
|
2015-07-22 15:38:41 +00:00
|
|
|
|
|
|
|
bool Transmitter::isTransmitting = false;
|
|
|
|
|
2015-07-24 12:41:10 +00:00
|
|
|
Transmitter::Transmitter(string filename, double frequency) :
|
|
|
|
readStdin(filename == "-")
|
2015-07-22 15:38:41 +00:00
|
|
|
{
|
|
|
|
ostringstream oss;
|
|
|
|
bool isBcm2835 = true;
|
|
|
|
|
|
|
|
FILE *pipe = popen("uname -m", "r");
|
|
|
|
if (pipe) {
|
|
|
|
char buffer[64];
|
|
|
|
string machine = "";
|
|
|
|
while (!feof(pipe)) {
|
|
|
|
if (fgets(buffer, 64, pipe)) {
|
|
|
|
machine += buffer;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
pclose(pipe);
|
|
|
|
|
|
|
|
if (machine != "armv6l\n") {
|
|
|
|
isBcm2835 = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-30 23:20:45 +00:00
|
|
|
int memFd;
|
|
|
|
if ((memFd = open("/dev/mem", O_RDWR | O_SYNC)) < 0) {
|
2015-07-22 15:38:41 +00:00
|
|
|
oss << "Cannot open /dev/mem (permission denied)";
|
|
|
|
errorMessage = oss.str();
|
|
|
|
throw exception();
|
2015-03-30 23:20:45 +00:00
|
|
|
}
|
|
|
|
|
2015-07-22 15:38:41 +00:00
|
|
|
void *peripheralsMap = mmap(NULL, 0x002FFFFF, PROT_READ | PROT_WRITE, MAP_SHARED, memFd, isBcm2835 ? 0x20000000 : 0x3F000000);
|
2015-03-30 23:20:45 +00:00
|
|
|
close(memFd);
|
2015-04-01 00:31:19 +00:00
|
|
|
if (peripheralsMap == MAP_FAILED) {
|
2015-07-22 15:38:41 +00:00
|
|
|
oss << "Cannot obtain access to peripherals (mmap error)";
|
|
|
|
errorMessage = oss.str();
|
|
|
|
throw exception();
|
2015-03-30 23:20:45 +00:00
|
|
|
}
|
|
|
|
|
2015-07-22 15:38:41 +00:00
|
|
|
peripherals = (volatile unsigned*)peripheralsMap;
|
|
|
|
|
2015-07-28 13:47:53 +00:00
|
|
|
AudioFormat *audioFormat;
|
2015-07-24 12:41:10 +00:00
|
|
|
if (!readStdin) {
|
|
|
|
waveReader = new WaveReader(filename);
|
2015-07-28 13:47:53 +00:00
|
|
|
audioFormat = waveReader->getFormat();
|
2015-07-24 12:41:10 +00:00
|
|
|
} else {
|
2015-07-28 13:47:53 +00:00
|
|
|
stdinReader = StdinReader::getInstance();
|
|
|
|
audioFormat = stdinReader->getFormat();
|
2015-07-30 19:55:35 +00:00
|
|
|
usleep(700000);
|
2015-07-24 12:41:10 +00:00
|
|
|
}
|
2015-07-28 13:47:53 +00:00
|
|
|
format = *audioFormat;
|
2015-04-04 14:04:08 +00:00
|
|
|
|
|
|
|
clockDivisor = (unsigned int)((500 << 12) / frequency + 0.5);
|
2015-03-30 23:20:45 +00:00
|
|
|
}
|
2015-04-01 00:31:19 +00:00
|
|
|
|
2015-07-22 15:38:41 +00:00
|
|
|
void Transmitter::play()
|
|
|
|
{
|
|
|
|
ostringstream oss;
|
|
|
|
|
|
|
|
if (isTransmitting) {
|
|
|
|
oss << "Cannot play, transmitter already in use";
|
|
|
|
errorMessage = oss.str();
|
|
|
|
throw exception();
|
|
|
|
}
|
2015-04-01 00:31:19 +00:00
|
|
|
|
2015-07-28 13:47:53 +00:00
|
|
|
frameOffset = 0;
|
2015-07-22 15:38:41 +00:00
|
|
|
isTransmitting = true;
|
|
|
|
pthread_t thread;
|
2015-04-01 21:12:41 +00:00
|
|
|
|
2015-07-28 13:47:53 +00:00
|
|
|
unsigned int bufferFrames = (unsigned int)((unsigned long long)format.sampleRate * BUFFER_TIME / 1000000);
|
2015-04-01 00:31:19 +00:00
|
|
|
|
2015-07-24 12:41:10 +00:00
|
|
|
buffer = (!readStdin) ? waveReader->getFrames(bufferFrames, frameOffset) : stdinReader->getFrames(bufferFrames);
|
2015-04-01 00:31:19 +00:00
|
|
|
|
2015-07-29 16:04:03 +00:00
|
|
|
vector<void*> params;
|
|
|
|
params.push_back((void*)&format.sampleRate);
|
|
|
|
params.push_back((void*)&clockDivisor);
|
|
|
|
params.push_back((void*)&frameOffset);
|
|
|
|
params.push_back((void*)&buffer);
|
|
|
|
params.push_back((void*)peripherals);
|
2015-07-28 22:25:44 +00:00
|
|
|
|
2015-07-29 16:04:03 +00:00
|
|
|
int returnCode = pthread_create(&thread, NULL, &Transmitter::transmit, (void*)¶ms);
|
2015-07-22 15:38:41 +00:00
|
|
|
if (returnCode) {
|
|
|
|
oss << "Cannot create new thread (code: " << returnCode << ")";
|
|
|
|
errorMessage = oss.str();
|
|
|
|
throw exception();
|
|
|
|
}
|
|
|
|
|
2015-07-22 21:28:07 +00:00
|
|
|
usleep(BUFFER_TIME / 2);
|
|
|
|
|
2015-07-28 23:36:54 +00:00
|
|
|
while(readStdin || !waveReader->isEnd()) {
|
2015-07-22 15:38:41 +00:00
|
|
|
if (buffer == NULL) {
|
2015-07-24 12:41:10 +00:00
|
|
|
buffer = (!readStdin) ? waveReader->getFrames(bufferFrames, frameOffset + bufferFrames) : stdinReader->getFrames(bufferFrames);
|
2015-07-22 15:38:41 +00:00
|
|
|
}
|
|
|
|
usleep(BUFFER_TIME / 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
isTransmitting = false;
|
|
|
|
pthread_join(thread, NULL);
|
|
|
|
}
|
|
|
|
|
2015-07-28 22:25:44 +00:00
|
|
|
void Transmitter::transmit(void *params)
|
2015-07-22 15:38:41 +00:00
|
|
|
{
|
|
|
|
unsigned long long current, start, playbackStart;
|
2015-07-28 23:36:54 +00:00
|
|
|
unsigned int offset, length, temp;
|
|
|
|
vector<float> *frames = NULL;
|
2015-07-22 15:38:41 +00:00
|
|
|
float *data;
|
|
|
|
|
2015-07-29 16:04:03 +00:00
|
|
|
unsigned int *sampleRate = (unsigned int*)(*((vector<void*>*)params))[0];
|
|
|
|
unsigned int *clockDivisor = (unsigned int*)(*((vector<void*>*)params))[1];
|
|
|
|
unsigned int *frameOffset = (unsigned int*)(*((vector<void*>*)params))[2];
|
|
|
|
vector<float> **buffer = (vector<float>**)(*((vector<void*>*)params))[3];
|
|
|
|
volatile unsigned *peripherals = (volatile unsigned*)(*((vector<void*>*)params))[4];
|
2015-07-28 22:25:44 +00:00
|
|
|
|
2015-07-28 13:47:53 +00:00
|
|
|
ACCESS(peripherals, 0x00200000) = (ACCESS(peripherals, 0x00200000) & 0xFFFF8FFF) | (0x01 << 14);
|
|
|
|
ACCESS(peripherals, 0x00101070) = (0x5A << 24) | (0x01 << 9) | (0x01 << 4) | 0x06;
|
2015-07-22 15:38:41 +00:00
|
|
|
|
|
|
|
playbackStart = ACCESS64(peripherals, 0x00003004);
|
|
|
|
current = playbackStart;
|
|
|
|
start = playbackStart;
|
|
|
|
|
|
|
|
while (isTransmitting) {
|
2015-07-28 23:36:54 +00:00
|
|
|
while (*buffer == NULL) {
|
2015-07-22 15:38:41 +00:00
|
|
|
usleep(1);
|
2015-04-03 13:34:53 +00:00
|
|
|
current = ACCESS64(peripherals, 0x00003004);
|
2015-04-01 21:12:41 +00:00
|
|
|
}
|
2015-07-28 23:36:54 +00:00
|
|
|
frames = *buffer;
|
|
|
|
*frameOffset = (current - playbackStart) * (*sampleRate) / 1000000;
|
|
|
|
*buffer = NULL;
|
2015-07-22 15:38:41 +00:00
|
|
|
|
|
|
|
length = frames->size();
|
|
|
|
data = &(*frames)[0];
|
|
|
|
|
|
|
|
offset = 0;
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
temp = offset;
|
|
|
|
if (offset >= length) {
|
|
|
|
offset -= length;
|
|
|
|
break;
|
|
|
|
}
|
2015-07-28 23:36:54 +00:00
|
|
|
ACCESS(peripherals, 0x00101074) = (0x5A << 24) | ((*clockDivisor) - (int)(round(data[offset] * 16.0)));
|
2015-07-22 15:38:41 +00:00
|
|
|
while (temp >= offset) {
|
|
|
|
usleep(1);
|
|
|
|
current = ACCESS64(peripherals, 0x00003004);
|
2015-07-28 23:36:54 +00:00
|
|
|
offset = (current - start) * (*sampleRate) / 1000000;
|
2015-07-22 15:38:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
start = ACCESS64(peripherals, 0x00003004);
|
|
|
|
delete frames;
|
2015-04-01 00:31:19 +00:00
|
|
|
}
|
2015-07-28 13:47:53 +00:00
|
|
|
|
|
|
|
ACCESS(peripherals, 0x00101070) = (0x5A << 24);
|
2015-04-01 00:31:19 +00:00
|
|
|
}
|
2015-03-30 23:20:45 +00:00
|
|
|
|
|
|
|
Transmitter::~Transmitter()
|
2015-04-01 00:31:19 +00:00
|
|
|
{
|
2015-07-22 15:38:41 +00:00
|
|
|
munmap(peripherals, 0x002FFFFF);
|
2015-07-24 12:41:10 +00:00
|
|
|
if (!readStdin) {
|
|
|
|
delete waveReader;
|
|
|
|
} else {
|
|
|
|
delete stdinReader;
|
|
|
|
}
|
2015-07-22 15:38:41 +00:00
|
|
|
}
|
|
|
|
|
2015-07-28 13:47:53 +00:00
|
|
|
AudioFormat& Transmitter::getFormat()
|
2015-07-22 15:38:41 +00:00
|
|
|
{
|
2015-07-24 12:41:10 +00:00
|
|
|
return format;
|
2015-03-30 23:20:45 +00:00
|
|
|
}
|