mirror of
https://github.com/F5OEO/rpitx.git
synced 2026-03-23 16:56:54 +01:00
Start processing data from pipe
ffmpeg is being weird. Wave files start with a header consisting of the text "RIFF", followed by 4 bytes for the length of the data chunk. But because we're streaming to a pipe, that value isn't known yet, so it just fills in 0xFFFFFFFF. libsndfile apparently doesn't like this and quits with the message "Error in WAV file. No 'data' chunk marker." I think I might need to drop libsndfile and manually process the data.
This commit is contained in:
@@ -18,46 +18,41 @@ struct module_state {
|
||||
static struct module_state _state;
|
||||
#endif
|
||||
|
||||
static void* sampleBase;
|
||||
static sf_count_t sampleLength;
|
||||
static sf_count_t sampleOffset;
|
||||
int streamFileNo = 0;
|
||||
static SNDFILE* sndFile;
|
||||
static int bitRate;
|
||||
|
||||
|
||||
// These methods used by libsndfile's virtual file open function
|
||||
static sf_count_t virtualSndfileGetLength(void* unused) {
|
||||
return sampleLength;
|
||||
assert(0 && "virtualSndfileGetLength should not be called");
|
||||
return 0;
|
||||
}
|
||||
static 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, ((char*)userData) + sampleOffset, numBytes);
|
||||
sampleOffset += numBytes;
|
||||
return numBytes;
|
||||
static sf_count_t virtualSndfileRead(void* const dest, sf_count_t count, void* const unused) {
|
||||
// So, the WAV file format starts with 12 bytes: the letters "RIFF", 4 byte
|
||||
// chunk size, and the letters "WAVE". Because we're streaming to a pipe, we
|
||||
// don't know the size of the file, and avconv just sticks in 0xFFFFFFFF, so
|
||||
// we'll just fudge a value in.
|
||||
if (count == 12) {
|
||||
read(streamFileNo, dest, count); // Just skip these
|
||||
memcpy(dest, "RIFF\x42\xB0\x0A\x00WAVE", count);
|
||||
return count;
|
||||
}
|
||||
|
||||
const ssize_t bytesRead = read(streamFileNo, dest, count);
|
||||
return bytesRead;
|
||||
}
|
||||
static sf_count_t virtualSndfileTell(void* const unused) {
|
||||
return sampleOffset;
|
||||
assert(0 && "virtualSndfileTell should not be called");
|
||||
return 0;
|
||||
}
|
||||
static sf_count_t virtualSndfileSeek(
|
||||
const sf_count_t offset,
|
||||
const int whence,
|
||||
void* const unused
|
||||
) {
|
||||
switch (whence) {
|
||||
case SEEK_CUR:
|
||||
sampleOffset += offset;
|
||||
break;
|
||||
case SEEK_SET:
|
||||
sampleOffset = offset;
|
||||
break;
|
||||
case SEEK_END:
|
||||
sampleOffset = sampleLength - offset;
|
||||
break;
|
||||
default:
|
||||
assert(!"Invalid whence");
|
||||
}
|
||||
return sampleOffset;
|
||||
assert(0 && "virtualSndfileSeek should not be called");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -66,7 +61,7 @@ typedef struct {
|
||||
uint32_t waitForThisSample;
|
||||
} samplerf_t;
|
||||
/**
|
||||
* Formats a chunk of an array of a mono 44k wav at a time and outputs IQ
|
||||
* Formats a chunk of an array of a mono 48k wav at a time and outputs IQ
|
||||
* formatted array for broadcast.
|
||||
*/
|
||||
static ssize_t formatRfWrapper(void* const outBuffer, const size_t count) {
|
||||
@@ -105,8 +100,8 @@ static ssize_t formatRfWrapper(void* const outBuffer, const size_t count) {
|
||||
}
|
||||
return numBytesWritten;
|
||||
}
|
||||
static void reset(void) {
|
||||
sampleOffset = 0;
|
||||
static void dummyFunction(void) {
|
||||
assert(0 && "dummyFunction should not be called");
|
||||
}
|
||||
|
||||
|
||||
@@ -114,31 +109,28 @@ static PyObject*
|
||||
_rpitx_broadcast_fm(PyObject* self, PyObject* args) {
|
||||
float frequency;
|
||||
|
||||
assert(sizeof(sampleBase) == sizeof(unsigned long));
|
||||
if (!PyArg_ParseTuple(args, "Lif", &sampleBase, &sampleLength, &frequency)) {
|
||||
if (!PyArg_ParseTuple(args, "if", &streamFileNo, &frequency)) {
|
||||
struct module_state *st = GETSTATE(self);
|
||||
PyErr_SetString(st->error, "Invalid arguments");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sampleOffset = 0;
|
||||
|
||||
SF_VIRTUAL_IO virtualIo = {
|
||||
.get_filelen = virtualSndfileGetLength,
|
||||
.seek = virtualSndfileSeek,
|
||||
.read = virtualSndfileRead,
|
||||
.write = NULL,
|
||||
.tell = virtualSndfileTell
|
||||
};
|
||||
SF_INFO sfInfo;
|
||||
sndFile = sf_open_virtual(&virtualIo, SFM_READ, &sfInfo, sampleBase);
|
||||
SF_VIRTUAL_IO virtualIo = {
|
||||
.get_filelen = virtualSndfileGetLength,
|
||||
.seek = virtualSndfileSeek,
|
||||
.read = virtualSndfileRead,
|
||||
.write = NULL,
|
||||
.tell = virtualSndfileTell
|
||||
};
|
||||
sndFile = sf_open_virtual(&virtualIo, SFM_READ, &sfInfo, NULL);
|
||||
if (sf_error(sndFile) != SF_ERR_NO_ERROR) {
|
||||
char message[100];
|
||||
snprintf(
|
||||
message,
|
||||
COUNT_OF(message),
|
||||
"Unable to open sound file: %s",
|
||||
sf_strerror(sndFile));
|
||||
message,
|
||||
COUNT_OF(message),
|
||||
"Unable to open pipe: %s",
|
||||
sf_strerror(sndFile));
|
||||
message[COUNT_OF(message) - 1] = '\0';
|
||||
struct module_state *st = GETSTATE(self);
|
||||
PyErr_SetString(st->error, message);
|
||||
@@ -153,8 +145,8 @@ _rpitx_broadcast_fm(PyObject* self, PyObject* args) {
|
||||
SIGWINCH, // Window resized
|
||||
0
|
||||
};
|
||||
pitx_run(MODE_RF, bitRate, frequency * 1000.0, 0.0, 0, formatRfWrapper, reset, skipSignals, 0);
|
||||
sf_close(sndFile);
|
||||
|
||||
pitx_run(MODE_RF, bitRate, frequency * 1000.0, 0.0, 0, formatRfWrapper, dummyFunction, skipSignals, 0);
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
@@ -166,10 +158,9 @@ static PyMethodDef _rpitx_methods[] = {
|
||||
_rpitx_broadcast_fm,
|
||||
METH_VARARGS,
|
||||
"Low-level broadcasting.\n\n"
|
||||
"Broadcast a WAV formatted 48KHz memory array.\n"
|
||||
"Broadcast a WAV formatted 48KHz file.\n"
|
||||
"Args:\n"
|
||||
" address (int): Address of the memory array.\n"
|
||||
" length (int): Length of the memory array.\n"
|
||||
" file_name (str): The WAV file to broadcast.\n"
|
||||
" frequency (float): The frequency, in MHz, to broadcast on.\n"
|
||||
},
|
||||
{NULL, NULL, 0, NULL}
|
||||
|
||||
@@ -8,6 +8,9 @@ import os
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
PIPE = 'pipe:1'
|
||||
DUMMY_FILE = 'dummy-file'
|
||||
|
||||
|
||||
def broadcast_fm(media_file_name, frequency):
|
||||
"""Play a media file's audio over FM."""
|
||||
@@ -29,36 +32,38 @@ def broadcast_fm(media_file_name, frequency):
|
||||
VideoCodec = libavwrapper.VideoCodec
|
||||
AudioCodec = libavwrapper.AudioCodec
|
||||
Stream = libavwrapper.AVConv
|
||||
Stream.add_option = Stream.add_parameter
|
||||
command = 'avconv'
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
'Broadcasting audio requires either avconv or ffmpeg to be installed'
|
||||
'Broadcasting audio requires either avconv or ffmpeg to be installed\n'
|
||||
'sudo apt install libav-tools'
|
||||
)
|
||||
|
||||
logger.debug('Using {}'.format(command))
|
||||
logger.debug('Using %s', command)
|
||||
|
||||
pipe_name = '/tmp/rpitx-fifo.wav'
|
||||
if not os.path.exists(pipe_name):
|
||||
os.mkfifo(pipe_name)
|
||||
input_media = Input(media_file_name)
|
||||
codec = AudioCodec('pcm_s16le').frequence(48000).channels(1)
|
||||
output_audio = Output(DUMMY_FILE, codec)
|
||||
stream = Stream(command, input_media, output_audio)
|
||||
command_line = list(stream)
|
||||
# The format needs to be specified manually because we're writing to
|
||||
# stderr and not a named file. Normally, it would infer the format from
|
||||
# the file name extension. Also, ffmpeg will only write to a pipe if it's
|
||||
# the last named argument.
|
||||
command_line.remove(DUMMY_FILE)
|
||||
command_line += ('-f', 'wav', PIPE)
|
||||
logger.debug('Running command "%s"', ' '.join(command_line))
|
||||
stream_process = subprocess.Popen(command_line, stdout=subprocess.PIPE)
|
||||
|
||||
def convert():
|
||||
"""Runs the conversion command and writes to a FIFO."""
|
||||
input_media = Input(media_file_name)
|
||||
codec = AudioCodec('pcm_s16le').frequence(48000).channels(1)
|
||||
output_audio = Output(pipe_name, codec)
|
||||
stream = Stream(command, input_media, output_audio)
|
||||
# Force overwriting existing file (it's a FIFO, that's what we want)
|
||||
if hasattr(stream, 'add_option'):
|
||||
stream.add_option('-y', '')
|
||||
else:
|
||||
stream.add_parameter('-y', '')
|
||||
logger.debug(str(stream).replace("'", '').replace(',', ''))
|
||||
def log_stdout():
|
||||
"""Log stdout from the stream process."""
|
||||
lines = stream.run()
|
||||
for line in lines:
|
||||
logger.debug(line)
|
||||
|
||||
thread = threading.Thread(target=convert)
|
||||
thread.start()
|
||||
#thread = threading.Thread(target=log_stdout).start()
|
||||
|
||||
logger.debug('Calling broadcast_fm')
|
||||
_rpitx.broadcast_fm(pipe_name, frequency)
|
||||
_rpitx.broadcast_fm(stream_process.stdout.fileno(), frequency)
|
||||
thread.join()
|
||||
|
||||
Reference in New Issue
Block a user