Gstreamer Filesink работает на командной строке, но не на Java-коде.
Я пытаюсь передавать аудио с Raspberry Pi на виртуальную машину.
К Raspberry Pi подключен микрофон, и его конвейер выглядит примерно так (информация об IP/ имени хоста отредактирована):
gst-launch-1.0 -ev alsasrc device=plughw:1,0 ! audioconvert ! rtpL24pay ! udpsink host=xxxxx port=xxxx
ВМ запускает этот конвейер:
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
Выполнение этого через командную строку прекрасно работает, когда я заканчиваю его с помощью Ctrl+C (в сочетании с ключом -e), и файл доступен для чтения. Однако я хотел бы, чтобы конвейер работал на Raspberry pi через командную строку, но использовал приложение Java для конвейера виртуальной машины. Это Java-приложение подключено к конечным точкам REST "/start" и "/stop". "/start" запускает конвейер, а "/ stop" должен останавливать конвейер и записывать в файл, но при достижении конечной точки "/ stop" файл имеет нулевой размер и не читается. Мой код в нижней части этого поста. Любые идеи по улучшению конвейера или как сделать файл читаемым было бы здорово. Сначала я думал, что это связано с тем, как я посылаю сообщение EOS, но не слишком уверен. Спасибо!
РЕДАКТИРОВАТЬ: Также забыл упомянуть, что я запускаю этот файл JAR в контейнере Docker, так что это может иметь отношение к портам, но опять же - не уверен.
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://stackru.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();
// }
}
}
1 ответ
У Decodebin есть динамические пэды-источники, и вам нужно связать их по мере их появления (они не существуют до начала потока, поскольку у декодера нет возможности узнать, что он собирается обработать и сколько пэдов ему понадобится).
В вашем случае, скорее всего, вам это не понадобится, так как вам не нужно декодировать, только деплоадер rtp. Если вы хотите сохранить его, обязательно зарегистрируйте pad-added
обратный вызов, и вы получите площадку, как только она будет создана. При обратном вызове вы должны связать его с остальной частью вашего конвейера (вы можете даже создать остальное в этой точке, если хотите).