mirror of
https://github.com/F5OEO/rpitx.git
synced 2026-03-23 16:56:54 +01:00
Add callback for processing FM files
This commit is contained in:
@@ -1,10 +1,74 @@
|
||||
#include <Python.h>
|
||||
#include <assert.h>
|
||||
#include <sndfile.h>
|
||||
|
||||
#include "../RpiTx.h"
|
||||
#include "../RpiGpio.h"
|
||||
|
||||
#define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))
|
||||
|
||||
|
||||
static void* sampleBase;
|
||||
static sf_count_t sampleLength;
|
||||
static sf_count_t sampleOffset;
|
||||
static SNDFILE* sndFile;
|
||||
|
||||
|
||||
// These methods used by libsndfile's virtual file open function
|
||||
sf_count_t virtualSndfileGetLength(void* unused) {
|
||||
return sampleLength;
|
||||
}
|
||||
sf_count_t virtualSndfileRead(void* const dest, const sf_count_t count, void* const userData) {
|
||||
const sf_count_t bytesAvailable = sampleLength - sampleOffset;
|
||||
const int numBytes = bytesAvailable > count ? count : bytesAvailable;
|
||||
memcpy(dest, userData, numBytes);
|
||||
sampleOffset += numBytes;
|
||||
return numBytes;
|
||||
}
|
||||
|
||||
|
||||
typedef struct {
|
||||
double frequency;
|
||||
uint32_t waitForThisSample;
|
||||
} samplerf_t;
|
||||
/**
|
||||
* Formats a chunk of an array of a mono 44k wav at a time and outputs IQ
|
||||
* formatted array for broadcast.
|
||||
*/
|
||||
ssize_t formatIqWrapper(void* const outBuffer, size_t count) {
|
||||
static float readBuffer[1024];
|
||||
const int excursion = 6000;
|
||||
samplerf_t samplerf;
|
||||
char* out = outBuffer;
|
||||
|
||||
int readCount;
|
||||
int k;
|
||||
int totalBytesToRead = count / sizeof(samplerf_t);
|
||||
int bytesToRead = totalBytesToRead;
|
||||
if (bytesToRead > COUNT_OF(readBuffer)) {
|
||||
bytesToRead = COUNT_OF(readBuffer);
|
||||
}
|
||||
int bytesWritten = 0;
|
||||
while ((readCount = sf_readf_float(sndFile, readBuffer, bytesToRead))) {
|
||||
for (k = 0; k < readCount; k++) {
|
||||
const int x = readBuffer[k];
|
||||
samplerf.frequency = x * excursion * 2.0;
|
||||
samplerf.waitForThisSample = 1e9 / 48000.0; //en 100 de nanosecond
|
||||
memcpy(&out[bytesWritten], &samplerf, sizeof(samplerf_t));
|
||||
bytesWritten += sizeof(samplerf_t);
|
||||
}
|
||||
totalBytesToRead -= bytesToRead;
|
||||
if (totalBytesToRead <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static PyObject*
|
||||
_rpitx_broadcast(PyObject* self, PyObject* args) {
|
||||
_rpitx_broadcast_fm(PyObject* self, PyObject* args) {
|
||||
int address;
|
||||
int length;
|
||||
float frequency;
|
||||
@@ -12,14 +76,29 @@ _rpitx_broadcast(PyObject* self, PyObject* args) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
setUpReadArray((void*)address, length);
|
||||
pitx_run(MODE_IQ, 44000, frequency, 0.0, 0, readArray, NULL);
|
||||
sampleBase = (void*)address;
|
||||
sampleLength = length;
|
||||
sampleOffset = 0;
|
||||
|
||||
SF_VIRTUAL_IO virtualIo = {
|
||||
.get_filelen = virtualSndfileGetLength,
|
||||
.seek = NULL,
|
||||
.read = virtualSndfileRead,
|
||||
.write = NULL,
|
||||
.tell = NULL
|
||||
};
|
||||
SF_INFO sfInfo ;
|
||||
sndFile = sf_open_virtual(&virtualIo, SFM_READ, &sfInfo, sampleBase);
|
||||
|
||||
pitx_run(MODE_IQ, 44000, frequency, 0.0, 0, formatIqWrapper, NULL);
|
||||
sf_close(sndFile);
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
static PyMethodDef _rpitx_methods[] = {
|
||||
{"broadcast", _rpitx_broadcast, METH_VARARGS, "Low-level broadcasting."},
|
||||
{"broadcast_fm", _rpitx_broadcast_fm, METH_VARARGS, "Low-level broadcasting."},
|
||||
{NULL, NULL, 0, NULL}
|
||||
};
|
||||
|
||||
|
||||
@@ -2,55 +2,43 @@
|
||||
|
||||
from pydub import AudioSegment
|
||||
import StringIO
|
||||
import array
|
||||
import _rpitx
|
||||
import wave
|
||||
import array
|
||||
import logging
|
||||
import os
|
||||
|
||||
|
||||
def play_fm(file_, frequency):
|
||||
def broadcast_fm(file_, frequency):
|
||||
"""Play a music file over FM."""
|
||||
|
||||
logging.basicConfig()
|
||||
logger = logging.getLogger('rpitx')
|
||||
|
||||
def _reencode(file_name):
|
||||
"""Returns a file-like object reencoded to the proper WAV format."""
|
||||
reencoded = StringIO.StringIO()
|
||||
# AudioSegment doesn't support context managers either
|
||||
"""Returns an AudioSegment file reencoded to the proper WAV format."""
|
||||
original = AudioSegment.from_file(file_name)
|
||||
if original.channels > 2:
|
||||
raise ValueError('Too many channels in sound file')
|
||||
if original.channels == 2:
|
||||
# TODO: Support stereo. For now, just overlay into mono.
|
||||
logger.info('Reducing stereo channels to mono')
|
||||
left, right = original.split_to_mono()
|
||||
original = left.overlay(right)
|
||||
|
||||
original.export(reencoded, format='wav', bitrate='44k')
|
||||
return reencoded
|
||||
if (
|
||||
original.frame_rate != 48000
|
||||
# TODO: There should be a better way to check if it's wav
|
||||
or not file_name.endswith('.wav')
|
||||
):
|
||||
logger.debug('Reencoding file')
|
||||
reencoded = StringIO.StringIO()
|
||||
original.export(reencoded, format='wav', bitrate='44k')
|
||||
return reencoded
|
||||
|
||||
encoded_file = None
|
||||
if isinstance(file_, str):
|
||||
if file_.endswith('.wav'):
|
||||
with open(file_) as raw_file:
|
||||
# wave.open doesn't support context managers, so we need to be
|
||||
# careful about closing the file
|
||||
wav_file = wave.open(raw_file, 'r')
|
||||
num_channels = wav_file.getnchannels()
|
||||
framerate = wav_file.getframerate()
|
||||
sample_width = wav_file.getsampwidth()
|
||||
wav_file.close()
|
||||
|
||||
if (
|
||||
num_channels != 1
|
||||
or framerate != 1
|
||||
or sample_width != 2
|
||||
):
|
||||
encoded_file = _reencode(file_)
|
||||
else:
|
||||
encoded_file = AudioSegment.from_file(file_)
|
||||
else:
|
||||
encoded_file = _reencode(file_)
|
||||
else:
|
||||
encoded_file = _reencode(file_)
|
||||
return original
|
||||
|
||||
encoded_file = _reencode(file_)
|
||||
raw_array = array.array('c')
|
||||
raw_array.fromstring(str(encoded_file))
|
||||
raw_array.fromstring(encoded_file.raw_data)
|
||||
array_address, length = raw_array.buffer_info()
|
||||
_rpitx.broadcast(array_address, length, frequency)
|
||||
_rpitx.broadcast_fm(array_address, length, frequency)
|
||||
|
||||
Reference in New Issue
Block a user