Gstreamer Filesink работает на командной строке, но не на Java-коде. - PullRequest
1 голос
/ 05 июня 2019

Я пытаюсь транслировать аудио с 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://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();
//        }
    }

}

1 Ответ

0 голосов
/ 13 июня 2019

Decodebin имеет динамические пэды источника, и вам нужно связать их, когда они появятся (они не существуют до запуска потока, поскольку у декодера нет возможности узнать, что он собирается обработать и сколько пэдов ему понадобится).

https://gstreamer.freedesktop.org/documentation/application-development/basics/pads.html?gi-language=c

В вашем случае вполне вероятно, что он вам не понадобится, поскольку вам не нужно декодировать, только деплоадер rtp.Если вы хотите сохранить его, обязательно зарегистрируйте обратный вызов pad-added, и вы получите площадку после ее создания.При обратном вызове вы должны связать его с остальной частью вашего конвейера (вы можете даже создать остальное в этой точке, если хотите).

...