kopia lustrzana https://github.com/F5OEO/rpitx
Add callback for processing FM files
rodzic
2df0f54626
commit
96db9cb3dc
4
setup.py
4
setup.py
|
@ -19,9 +19,9 @@ setup(
|
|||
'src/RpiDma.c',
|
||||
'src/RpiGpio.c',
|
||||
],
|
||||
extra_link_args=['-lrt'],
|
||||
extra_link_args=['-lrt', '-lsndfile'],
|
||||
),
|
||||
],
|
||||
package_dir={'': 'src/python'},
|
||||
install_requires=['wave', 'pydub'],
|
||||
install_requires=['pydub'],
|
||||
)
|
||||
|
|
21
src/RpiTx.c
21
src/RpiTx.c
|
@ -1032,27 +1032,6 @@ static void resetFile(void) {
|
|||
}
|
||||
|
||||
|
||||
static void* arrayBaseAddress;
|
||||
static int arrayLength;
|
||||
static int arrayPosition;
|
||||
/** Wrapper around reading from memory with an interface similar to read. */
|
||||
ssize_t readArray(void *buffer, const size_t count) {
|
||||
if (arrayPosition >= arrayLength) {
|
||||
return 0;
|
||||
}
|
||||
const int left = arrayLength - arrayPosition;
|
||||
const int numBytesToCopy = left > count ? count : left;
|
||||
memcpy(buffer, arrayBaseAddress + arrayPosition, numBytesToCopy);
|
||||
arrayPosition += numBytesToCopy;
|
||||
return numBytesToCopy;
|
||||
}
|
||||
void setUpReadArray(void* baseAddress, size_t length) {
|
||||
arrayBaseAddress = baseAddress;
|
||||
arrayLength = length;
|
||||
arrayPosition = 0;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
main(int argc, char* argv[])
|
||||
{
|
||||
|
|
|
@ -24,8 +24,4 @@ int pitx_run(
|
|||
);
|
||||
|
||||
|
||||
/** Wrapper around reading from memory with an interface similar to read. */
|
||||
ssize_t readArray(void *buffer, const size_t count);
|
||||
void setUpReadArray(void* baseAddress, size_t length);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -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)
|
||||
|
|
Ładowanie…
Reference in New Issue