caprica / vlcj

Java framework for the vlc media player
http://www.capricasoftware.co.uk/projects/vlcj
1.13k stars 259 forks source link

VLCJ events #1084

Closed courteous closed 3 years ago

courteous commented 3 years ago

Hello Mark,

I realized that #1082 is closed and decided to describe the problem in more details.

The problem i am describing can be reproduced using the same simple project that has basically 2 main files i.e. vlcjTestMute

notice that i am starting the player with the "-vvv" option i.e."new MediaPlayerFactory("-vvv");"

this is the most important part i.e. :

        MediaApi mediaApi =  this.embeddedMediaPlayer.media();
        String sourceOfMedia = "dshow://" ;

        String[] options = {    

                                ":dshow-vdev=" + cameraDevice,    
                                ":dshow-adev=" + microfonDevice,  
                                ":sout=#transcode{vcodec=h264,fps=24,vb=750,scale=Auto,width=640,height=360,acodec=mp4a,ab=128,channels=2,samplerate=44100,threads=8,scodec=none}"
                                + ":duplicate{"
                                + "dst=standard{access=udp,mux=ts,dst=172.26.15.107:5555},"
                                + "dst=display"
                                + "}"

                                };

After the player is started i am able to see in the logs that the player is working i.e. transcoding is working as well and i see the UDP packets in wireshark. The sout string is also correctly processed i.e. here is the output of the "-vvv" option debugging

[000001f428d1a2e0] main stream output debug: using sout chain=`transcode{vcodec=h264,fps=24,vb=750,scale=Auto,width=640,height=360,acodec=mp4a,ab=128,channels=2,samplerate=44100,threads=8,scodec=none}:duplicate{dst=standard{access=udp,mux=ts,dst=172.26.15.107:5555},dst=display}'
[000001f428d1a2e0] main stream output debug: stream=`duplicate'
[000001f4315d1cb0] main stream out debug: looking for sout stream module matching "duplicate": 46 candidates
[000001f4315d1cb0] duplicate stream out debug: creating 'duplicate'
[000001f4315d1cb0] duplicate stream out debug:  * adding `standard{access=udp,mux=ts,dst=172.26.15.107:5555}'
[000001f428d1a2e0] main stream output debug: stream=`standard'
[000001f4315d1200] main stream out debug: looking for sout stream module matching "standard": 46 candidates
[000001f4315d1200] main stream out debug: set config option: sout-standard-access to udp
[000001f4315d1200] main stream out debug: set config option: sout-standard-mux to ts
[000001f4315d1200] main stream out debug: set config option: sout-standard-dst to 172.26.15.107:5555
[000001f428d1a400] main access out debug: looking for sout access module matching "udp": 22 candidates
[000001f428d1a400] main access out debug: net: connecting to [172.26.15.107]:5555
[000001f428d1a400] udp access out debug: source: 172.26.15.105 port 52719
[000001f428d1a400] udp access out debug: destination: 172.26.15.107 port 5555
[000001f428d1a400] main access out debug: using sout access module "udp"
[000001f428dde4f0] main mux debug: looking for sout mux module matching "ts": 22 candidates
[000001f428dde4f0] mux_ts mux debug: shaping=200000 pcr=70000 dts_delay=400000
[000001f428dde4f0] main mux debug: using sout mux module "mux_ts"
[000001f428d1a2e0] main stream output debug: muxer support adding stream at any time
[000001f428d1a2e0] main stream output debug: muxer prefers to wait for all ES before starting to mux
[000001f4315d1200] stream_out_standard stream out debug: using `udp/ts://172.26.15.107:5555'
[000001f4315d1200] main stream out debug: using sout stream module "stream_out_standard"
[000001f4315d1cb0] duplicate stream out debug:  * adding `display'
[000001f428d1a2e0] main stream output debug: stream=`display'
[000001f4315d1b80] main stream out debug: looking for sout stream module matching "display": 46 candidates
[000001f4315d1b80] main stream out debug: using sout stream module "display"
[000001f4315d1cb0] main stream out debug: using sout stream module "duplicate"
[000001f428d1a2e0] main stream output debug: stream=`transcode'
[000001f4315d17f0] main stream out debug: looking for sout stream module matching "transcode": 46 candidates
[000001f4315d17f0] main stream out debug: set config option: sout-transcode-vcodec to h264
[000001f4315d17f0] main stream out debug: set config option: sout-transcode-fps to 24
[000001f4315d17f0] main stream out debug: set config option: sout-transcode-vb to 750
[000001f4315d17f0] main stream out debug: set config option: sout-transcode-scale to Auto
[000001f4315d17f0] main stream out debug: set config option: sout-transcode-width to 640
[000001f4315d17f0] main stream out debug: set config option: sout-transcode-height to 360
[000001f4315d17f0] main stream out debug: set config option: sout-transcode-acodec to mp4a
[000001f4315d17f0] main stream out debug: set config option: sout-transcode-ab to 128
[000001f4315d17f0] main stream out debug: set config option: sout-transcode-channels to 2
[000001f4315d17f0] main stream out debug: set config option: sout-transcode-samplerate to 44100
[000001f4315d17f0] main stream out debug: set config option: sout-transcode-threads to 8
[000001f4315d17f0] main stream out debug: set config option: sout-transcode-scodec to none
[000001f4315d17f0] stream_out_transcode stream out debug: Checking codec mapping for mp4a got mp4a 
[000001f4315d17f0] stream_out_transcode stream out debug: codec audio=mp4a 44100Hz 2 channels 128Kb/s
[000001f4315d17f0] stream_out_transcode stream out debug: Checking video codec mapping for h264 got h264 
[000001f4315d17f0] stream_out_transcode stream out debug: codec video=h264 640x360 scaling: 0.000000 750kb/s
[000001f4315d17f0] stream_out_transcode stream out debug: Checking spu codec mapping for none got none 
[000001f4315d17f0] stream_out_transcode stream out debug: codec spu=none
[000001f4315d17f0] main stream out debug: using sout stream module "stream_out_transcode"
[000001f4315badb0] main input debug: using timeshift granularity of 50 MiB
...
[000001f4315badb0] main input debug: `dshow://' gives access `dshow' demux `any' path `'
[000001f428d1a520] main input source debug: creating demux: access='dshow' demux='any' location='' file='(null)'
[000001f4315fb9b0] main demux debug: looking for access_demux module matching "dshow": 30 candidates
[000001f4315fb9b0] dshow demux debug: dshow-vdev: Logitech Webcam C930e
[000001f4315fb9b0] dshow demux debug: dshow-adev: Mikrofon (2- Logitech Webcam C930e)
[000001f4315fb9b0] dshow demux debug: found device: Integrated Camera
[000001f4315fb9b0] dshow demux debug: found device: Logitech Webcam C930e
[000001f4315fb9b0] dshow demux debug: asking for device: Logitech Webcam C930e
[000001f4315fb9b0] dshow demux debug: asked for Logitech Webcam C930e, binding to Logitech Webcam C930e
[000001f4315fb9b0] dshow demux debug: using device: Logitech Webcam C930e

basically all options are processed correctly by the libvlc . However the only difference here is that when executing an mute event trough vlcj for some reason there is no calback which is comming from the NativeEventManager that i described in issue #1082 i.e. when the muting fiction is working correctly that callback is being seen. That callback is responsible to call the overwritten methods from the MediaPlayerFactory class. In this case that is not happening.

Below is the normal process

a) we execute "audioApi.setMute(true);" b) then we see the call to "libvlc_audio_set_mute(mediaPlayerInstance, mute ? 1 : 0);" d) then we get a callback from the libvlc i.e. see the screenshot below e) then the overwritten method muted in the MediaPlayerEventAdapter is being executed.

grafik

in this case that callback from NativeEventManager is missing. Do you have any idea why or how to troubleshoot that further?

caprica commented 3 years ago

I really don't know. Perhaps you've found some problem with LibVLC's media players when using multiple souts.

Maybe trying changing the order of your dst= so that the "display" dst is first.

courteous commented 3 years ago

Hello Mark just tried changing the dst i.e.

":sout=#transcode{vcodec=h264,fps=24,vb=750,scale=Auto,width=640,height=360,acodec=mp4a,ab=128,channels=2,samplerate=44100,threads=8,scodec=none}"
                                + ":duplicate{"
                                + "dst=display"
                                + ","
                                + "dst=standard{access=udp,mux=ts,dst=172.26.15.107:5555}"
                                + "}"

still the same result.

Do you think that this is something that is on the vlcj side or LibVLC. The way i see it this NativeEventManager callback is the reason but this one can come either from LibVLC or vlcj . Any ideas how to debug something like this?

caprica commented 3 years ago

This issue can be demonstrated by simply just using the #transcode string or not, the duplicate part is not relevant to the issue.

And indeed there are no audio events when using #transcode.

caprica commented 3 years ago

My LibVLC discord contacts have no answer for this.

But it seems enabling transcoding prevents, on the native side, audio controls and audio events from working correctly.

Debugging it has to happen IMO on the native VLC side. Since I am not so familiar with gdb etc, I'd be editing VLC sources to add some console log output to work out what was going on. Unfortunately, I suspect this is something deep within VLC.

The VLC devs would say that none of these sout options are supported for use with LibVLC.

courteous commented 3 years ago

Hello Mark, thank you for the feedback and for your support. It is kind of strange that "VLC devs would say that none of these sout options are supported" since they are the once that put those options there. I am not sure about that. Even the VLC software itself provides those options to you in a textual form once you want to do a streaming. I belive that those options are supported since they are mentioned in the official wiki of the vlc i.e. vlc wiki

Anyway, I would like to test something this Friday i.e. i want to see if i have two separate embedded VLC players i.e. one with transcode and another without it, if i can control the second one and if the second is able to receive the events. Those should be on separate threads. I would test that this Friday and report back.
I do agree that this is most likely something on the LibVLC side. I think what we can try next is to see which part of LibVLC is responsible for that NativeEventManager callback. If we can find that, then we know what is "expected" behavior, and then we set some breakpoints in LibVLC and dump the callback stack. From there on, we should be able to find the place where that stack is different. Just my 2 cents.

caprica commented 3 years ago

The sout options are not supported via LibVLC. If there is no API for the sout options then if it works it's a bonus, but it comes with no guarantees.

They are of course supported by VLC itself.

VLC is not responsible for the NativeEventManager callback at all - there is not necessarily a direct analog between vlcj code and VLC code.

courteous commented 3 years ago

I suppose you are right that those options are not supported. I was hoping for the best :) . About the callback: What i meant is the part of the code that is responsible for calling the NativeEventManager callback. My understanding so far is that this is somewhere in LibVLC, but i am not sure about that, maybe you can elaborate on it.

caprica commented 3 years ago

There are event managers in LibVLC. But I think the problem will go much deeper than that. It might be possible to track it down but it will take some work I reckon.

courteous commented 3 years ago

Well this is a real bump, i was thinking that i can use Java Native Sound System and mute the application using the getSourceDataLine directly however it turns out that this is not possible because all all lines on my Laptop are missing i.e. the BooleanControl.Type.MUTE is not supported. Then i printed all of the Mixers and all SourceDataLines, and it turns out that my laptop has 13 mixers and non of them support the BooleanControl.Type.MUTE type. In fact my all mixers that have SourceDataLines the array "Control [] controls = lineSpeaker.getControls();" is empty I really did not expected this unfortunate event. This seems to be dependent of Java and hardware. Here is the code if anyone one to try this. Mark do you have any other idea how to mute that sound while transcoding?


    @FXML
    private void muteButtonAction(ActionEvent event) {

        // this is the laptop default speaker.
        int reporterMixerNumber = 7; 
        Mixer.Info[] mixInfors = AudioSystem.getMixerInfo();
        final Mixer.Info mxInfo = mixInfors[reporterMixerNumber]; 

                String mixerName = mxInfo.getName();
        String mixerDescription =  mxInfo.getDescription();

        System.out.println("MIXER name : " + mixerName);
        System.out.println("\t-----Mixer Description :" + mixerDescription);
        System.out.println("\t-----Mixer Vendor :" + mxInfo.getVendor());
        System.out.println("\t-----Mixer version :" + mxInfo.getVersion());

        Mixer speakerMixer = AudioSystem.getMixer(mxInfo);

        Line.Info[] sourceLineInfos = speakerMixer.getSourceLineInfo();
        Line.Info lineInfoLineZero = sourceLineInfos[0];

        SourceDataLine speakers = null;
        AudioFormat format = new AudioFormat(8000.0f, 16, 1, true, true);

        try {

//           
             speakers = AudioSystem.getSourceDataLine(format, mxInfo);
        } catch (LineUnavailableException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("\t\t-----Line Info []  : " + lineInfoLineZero );

        Line lineSpeaker = null;
        try {
            lineSpeaker = speakerMixer.getLine(lineInfoLineZero);

            Boolean IsMuteSupported = lineSpeaker.isControlSupported(BooleanControl.Type.MUTE);
            Control [] controls = lineSpeaker.getControls();
            if(IsMuteSupported) {
                Control control = lineSpeaker.getControl(BooleanControl.Type.MUTE);

                BooleanControl bc = (BooleanControl) control;

                if (bc != null) {
                    bc.setValue(true); 
                }
            }

        } catch (LineUnavailableException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }
courteous commented 3 years ago

The interesting thing is that the VLC 3.0.16 itself can do transcoding, streaming and mute control simultaneously. I just tested this from the VLC directlly, and i can mute and unmute white transcoding.

caprica commented 3 years ago

I'm not so familiar with JavaSound, but if there isn't a boolean mute control can't you just use a volume control and set that to 0.

But then I see you say they have no controls at all, which can't be right. So something else must be wrong there.

To mute while transcoding, well, do you really need to duplicate the sout to "display"?

courteous commented 3 years ago

Hello Mark,

And about the missing controls the Java doc mentions explicitly that that the controls might be missing. And i am afraid this is the case with my particular laptop. About the duplicate "display", why do you ask? Is there a constellation where the events will work without the duplicate display? What would be if i do not need the duplicate display.

I was even thinking about creating an instance of the embedded player that will do only transcoding, and another instance that will subscribe to the stream and play it locally (trough local multicast), however unfortunately i can not use multicast, hence the unicast address in the example.

caprica commented 3 years ago

The question is why are you transcoding and playing it on the local display? Do you really need to do that?

I can't answer your other questions, try it and see...

Maybe you will need to resort to making native calls to whatever OS audio mixer you're using (Alsa, Windows etc).

The bottom line here I think is that there is probably nothing you can do from the vlcj perspective.

caprica commented 3 years ago

I don't think there's anything more can be added to this. The issue can be reopened if something new turns up.