I'm trying to stream audio from a Raspberry Pi to a VM.
The Raspberry Pi has a microphone plugged into it and its pipeline is like so (IP/hostname info redacted):
gst-launch-1.0 -ev alsasrc device=plughw:1,0 ! audioconvert ! rtpL24pay ! udpsink host=xxxxx port=xxxx
The VM is running this pipeline:
gst-launch-1.0 -ev udpsrc port=xxxx caps="application/x-rtp, media=(string)audio, clock-rate=(int)44100, encoding-name=(string)L24, encoding-params=(string)2, channels=(int)2, payload=(int)96, ssrc=(uint)636287891, timestamp-offset=(uint)692362821, seqnum-offset=(uint)11479" ! rtpL24depay ! decodebin ! audioconvert ! wavenc ! filesink location=test.wav
Running this via command line works just fine when I end it with Ctrl+C (coupled with the -e switch), and the file is readable. What I'd like to do, however, is keep the pipeline running on the Raspberry pi via command line, but use a Java application for the VM's pipeline. This Java application is hooked to REST endpoints "/start" and "/stop". "/start" starts the pipeline and "/stop" should stop the pipeline and write to file, but when hit the "/stop" endpoint the file is of size zero and is unreadable. My code is at the bottom of this post. Any ideas on improving the pipeline or how to make the file readable would be great. My initial thinking was that it has to do with how I'm sending an EOS message, but not too sure. Thank you!
EDIT: Also forgot to mention that I'm running this JAR file in a Docker container, so it may have to do with ports, but again--not sure.
package service.rest.controllers;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.freedesktop.gstreamer.*;
import org.freedesktop.gstreamer.event.EOSEvent;
import org.freedesktop.gstreamer.message.EOSMessage;
import org.freedesktop.gstreamer.message.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import service.config.ClientSettings;
import service.config.FileSettings;
import service.postgres.Database;
import service.postgres.ExecuteDatabase;
import java.text.SimpleDateFormat;
import java.util.Date;
//pipeline running on pi@raspberrypi:
//gst-launch-1.0 -ev alsasrc device=plughw:1,0 ! audioconvert ! rtpL24pay ! udpsink host=xxxxx port=xxxx
@RestController
public class AudioCaptureController {
@Autowired
public Database database;
@Autowired
ExecuteDatabase db_executor;
@Autowired
ClientSettings clientSettings;
@Autowired
FileSettings fileSettings;
private static final Logger LOGGER = LogManager.getLogger(AudioCaptureController.class.getName());
private static final String startTemplate = "Pipeline started at %s.";
private static final String stopTemplate = "File recorded for time window %s to %s.";
private static final SimpleDateFormat ft = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private Pipeline pipe;
private Date startTime;
private int port;
private int defaultLength;
private int defaultRecordingDuration;
private String defaultDirectory;
public AudioCaptureController() {
}
/**
* Initializes GStreamer pipeline.
* udpsrc ! rtpL24depay ! decodebin ! audioconvert ! wavenc ! filesink
*/
public void init() {
port = clientSettings.getUdp_port();
defaultLength = fileSettings.getDefault_length();
defaultRecordingDuration = fileSettings.getDefault_recording_duration();
defaultDirectory = fileSettings.getDefault_directory();
Gst.init("Receiver");
//CREATE ELEMENTS
Element source = ElementFactory.make("udpsrc", "source");
Element depayloader = ElementFactory.make("rtpL24depay", "depayloader");
Element decoder = ElementFactory.make("decodebin", "decoder");
Element converter = ElementFactory.make("audioconvert", "converter");
Element encoder = ElementFactory.make("wavenc", "encoder");
Element sink = ElementFactory.make("filesink", "sink");
//CONFIGURE ELEMENTS
Caps caps = Caps.fromString("application/x-rtp, " +
"media=(string)audio, " +
"clock-rate=(int)44100, " +
"encoding-name=(string)L24, " +
"encoding-params=(string)2, " +
"channels=(int)2, " +
"payload=(int)96, " +
"ssrc=(uint)636287891, " +
"timestamp-offset=(uint)692362821, " +
"seqnum-offset=(uint)11479");
source.set("port", port);
source.setCaps(caps);
//GENERATE WAV FILE - **Currently generating only one file**
//todo: need a way to save specific file names. probably have to pause and restart the stream each time.
//consider splitting the file post-processing
//can't use multifilesink or splitmuxsink b/c no native support for wav
//https://stackoverflow.com/questions/25662392/gstreamer-multifilesink-wav-files-splitting
sink.set("location", defaultDirectory + "test.wav");
// sink.set("location", "test.wav");
//SET UP PIPELINE
pipe = new Pipeline();
pipe.addMany(source, depayloader, decoder, converter, encoder, sink);
//LINK PADS
source.link(depayloader);
depayloader.link(decoder);
decoder.link(converter);
converter.link(encoder);
encoder.link(sink);
//HANDLE EOS/ERROR/WARNING ON THE BUS
Bus bus = pipe.getBus();
bus.connect((Bus.EOS) gstObject -> System.out.println("EOS " + gstObject));
bus.connect((Bus.ERROR) (gstObject, i, s) -> System.out.println("ERROR " + i + " " + s + " " + gstObject));
bus.connect((Bus.WARNING) (gstObject, i, s) -> System.out.println("WARN " + i + " " + s + " " + gstObject));
bus.connect((Bus.EOS) obj -> {
pipe.stop();
Gst.deinit();
Gst.quit();
});
}
/**
* Starts the GStreamer pipeline.
*/
@RequestMapping("/start")
public String startRecording() {
//START PIPELINE
pipe.play();
startTime = new Date(System.currentTimeMillis());
LOGGER.info(String.format(startTemplate, ft.format(startTime)));
return String.format(startTemplate, ft.format(startTime));
}
/**
* Stops the GStreamer pipeline and pushes the file to database.
*/
@RequestMapping("/stop")
public String stopRecording() {
// if (pipe.isPlaying()) { //might have to comment this out
// pipe.stop();
pipe.getBus().post(new EOSMessage(pipe.getS));
// Gst.quit();
Date endTime = new Date(System.currentTimeMillis());
String filePath = defaultDirectory + "test.wav";
db_executor.insertRecord(database.getConnection(), ft.format(startTime), ft.format(endTime), filePath);
LOGGER.info(String.format(stopTemplate, ft.format(startTime), ft.format(endTime)));
return String.format(stopTemplate, ft.format(startTime), ft.format(endTime));
// } else {
// LOGGER.info("Pipeline is already at state " + pipe.getState());
// return "Pipeline is already at state " + pipe.getState();
// }
}
}