muammar / mkchromecast

Cast macOS and Linux Audio/Video to your Google Cast and Sonos Devices
http://mkchromecast.com
Other
2.21k stars 137 forks source link

Can't stream with Wi-Fi #27

Closed ao-il closed 4 years ago

ao-il commented 7 years ago

The performance over Wi-Fi seems very poor, but the case is different with a wired connection (100Mb/s). What is the minimum bandwidth requirement? Also, the latency slightly improves when other devices are disconnected from the network. Another issue is that it seems to need a restart when no media is streamed for a while. Regarding latency, there must be some algorithm to fix this, something like buffering and/or using smaller packets (I'm no expert), since streaming with other mobile apps is without this issue.

muammar commented 7 years ago

The performance over Wi-Fi seems very poor, but the case is different with a wired connection (100Mb/s). What is the minimum bandwidth requirement?

That depends on the routers. I had problems before with an old router. That of course may not be your problem.

Also, the latency slightly improves when other devices are disconnected from the network. Another issue is that it seems to need a restart when no media is streamed for a while.

I am aware of this. If you don't stream anything for a while, you need to restart. Now, in that case I am not sure if it is the Google cast device that goes to sleep because nothing is playing.

Regarding latency, there must be some algorithm to fix this, something like buffering and/or using smaller packets (I'm no expert), since streaming with other mobile apps is without this issue.

Try the --chuck-size flag:

--chunk-size CHUNK_SIZE
                        Set the chunk size for streaming in the Flask server. Default to 1024. This
                            python mkchromecast.py --encoder-backend ffmpeg -c ogg -b 128 --chunk-size 2048
                            python mkchromecast.py --encoder-backend avconv -c ogg -b 128 --chunk-size 512

Let me know if it improves the lag.

ao-il commented 7 years ago

Thanks for your reply.

I can assure you that it is not my router that makes it not to work on Wi-Fi, as I can stream flawlessly from other mobile devices. The problem is that the playback keeps getting interrupted, making it impossible to play anything.

I have doubled the chunk-size but it has not improved. Should I use something higher than 2048? I have also noticed that it (sometimes) plays some cached chunk a few minutes afters it has stopped.

May I ask if you are employing some standard streaming protocol? Or maybe there isn't such thing.

ao-il commented 7 years ago

Shouldn't I be reducing the chunk size instead? Anyway, I have used both, still the same. It now plays some chunk after it has stopped.

muammar commented 7 years ago

I can assure you that it is not my router that makes it not to work on Wi-Fi, as I can stream flawlessly from other mobile devices. The problem is that the playback keeps getting interrupted, making it impossible to play anything.

Can you elaborate on how those devices do the streaming?. You are not talking about Spotify or something like that, isn't it?.

I have doubled the chunk-size but it has not improved. Should I use something higher than 2048? I have also noticed that it (sometimes) plays some cached chunk a few minutes afters it has stopped.

The size of the chunk had to be decreased as you said. I put the option because of the problems with my old router, but it didn't help too much.

May I ask if you are employing some standard streaming protocol? Or maybe there isn't such thing.

What I can tell you is that in Linux using ffmpeg implies different layers to stream to the google cast. Let me explain. First I create the sink (pulseaudio being a layer on top of ALSA), and ffmpeg reads from it and dumps to /dev/stdout. Then, python-flask creates a local HTTP server that takes as input the ffmpeg stdout. I think that all of that adds up making the lag occur.

I think that the problem could be in the flask side, maybe that is not the best way of doing the streaming. I am thinking to provide a node server and verify if this helps. Do you know something about programming in node.js?.

ao-il commented 7 years ago

I have used different chromecast apps (pulsar for example) from play store, even the Google's play music app for android, with no noticeable lag.

Yes, I think the issue is with the transport protocol not the encoder. Http is connection-oriented and reliable, but slow. Udp is connectionless and fast, but unreliable. Maybe if you move the packets over the latter, that would solve the problem. I think I read somewhere people advocating a packet size of 1500 bytes and that large packet size would cause fragmentation (which explains the extra chunk being played after stopping the playback). Have you heard about gstreamer? Not really sure how it works but read somewhere that it is fast and without latency. There are many protocols out there for steaming. I also read that ffmpeg can work with ffserver. I'm just mentioning, I have no idea.

You mentioned using pulseaudio, I thought I was using ALSA directly? I am not sure I know about the node server, but I am familiar with little javascript.

muammar commented 7 years ago

I have used different chromecast apps (pulsar for example) from play store, even the Google's play music app for android, with no noticeable lag.

Google play music app sends directly the streaming from google servers (like spotify does when you use the app in the phone), therefore, in that case, your phone is just a remote control and nothing else. I don't know if pulsar plays local files and if it does, then the streaming server is doing the difference here.

Yes, I think the issue is with the transport protocol not the encoder. Http is connection-oriented and reliable, but slow. Udp is connectionless and fast, but unreliable. Maybe if you move the packets over the latter, that would solve the problem. I think I read somewhere people advocating a packet size of 1500 bytes and that large packet size would cause fragmentation (which explains the extra chunk being played after stopping the playback).

I also think the problem is related to python-flask not been adequate for this task. Sadly, Google cast devices cannot read from UDP streams... I have tried it. It has to be a http://something.

Have you heard about gstreamer? Not really sure how it works but read somewhere that it is fast and without latency. There are many protocols out there for steaming. I also read that ffmpeg can work with ffserver. I'm just mentioning, I have no idea.

Yes, I am working on that. I think it is operational in the devel branch, but gstreamer is just being used to capture audio. I cannot use the UDP stream pipeline because google cast does not read them (could you confirm this?).

You mentioned using pulseaudio, I thought I was using ALSA directly? I am not sure I know about the node server, but I am familiar with little javascript.

If you are not using the --alsa-device and the .asoundrc file then you are a pulseaudio user. I really don't remember if you were the one who reported adding ALSA support before.

ao-il commented 7 years ago

I have been playing local files and not from Google servers. Using normal http must result in some lag because of the "ceremonies" involved. Udp is not good for audio because we want all the bits to arrive. So, we must need some enhancement to make http work. This will probably involve transmitting smaller packets (progressive download) to reduce latency. What about ffmpeg with ffserver? I think it uses rtp.

I use the .asoundrc, so I am not using pulseaudio.

You mean to check whether the cca supports udp?

ao-il commented 7 years ago

I just want to ask, are you using http headers? I have read somewhere where they passed arguments with url instead.

muammar commented 7 years ago

I have been playing local files and not from Google servers. Using normal http must result in some lag because of the "ceremonies" involved. Udp is not good for audio because we want all the bits to arrive. So, we must need some enhancement to make http work. This will probably involve transmitting smaller packets (progressive download) to reduce latency. What about ffmpeg with ffserver? I think it uses rtp.

That's why I added a chunk option. But it does not work as expected. I thought about ffmpeg + ffserver but rtp is not supported either :(.

I use the .asoundrc, so I am not using pulseaudio.

You are right. Sorry for the mistake.

You mean to check whether the cca supports udp?

Yes. But I think it does not.

I just want to ask, are you using http headers? I have read somewhere where they passed arguments with url instead.

The local http server is contained in these functions: https://github.com/muammar/mkchromecast/blob/master/mkchromecast/audio.py#L478. And the Google Cast is instructed to point to that local server here: https://github.com/muammar/mkchromecast/blob/master/mkchromecast/cast.py#L305. So basically, when the chromecast knows what's the IP of the local server in your laptop, it requests the headers and the flask server send them back and the stream starts.

muammar commented 7 years ago

What it would make a difference is if the data dumped to stdout be segmented?. Maybe that's lacking in the ffmpeg command.

ao-il commented 7 years ago

I think it should improve if you get the stream in a progressive approach. Eg:

Get 0-4 3 min Get 4-8 3 min ...

muammar commented 7 years ago

@ao-il I have added segmented audio in the ffmpeg command: https://github.com/muammar/mkchromecast/commit/9677ba2536089b9f413c45bbcb3cdb8d1ab36cc4#diff-890309d9973a3076001e4163695a23ba. Could you please test it, and tell me if it improves anything?

Use the ffmpeg backend and the ogg codec. I was able to see audio starting in two seconds, which is the segmented time I chose.

ao-il commented 7 years ago

Slightly better, but still with up to 20s lag. Still playing old chunk of previous audio on resume/track change. I have removed the chunk-size argument so it uses default, but still the same. Shouldn't it suffice to have at least 2mb/s? I have 65 on Wi-Fi and 100 on wired connection. If you are using something much higher, you may not notice the flaws.

muammar commented 7 years ago

Slightly better, but still with up to 20s lag. Still playing old chunk of previous audio on resume/track change. I have removed the chunk-size argument so it uses default, but still the same.

Summarizing: problem is when stopping/resuming track while casting. Another possible way to fix this would be to monitor when silence is being streamed and stop ffmpeg, and resume again(?). But it sounds complicated.

Shouldn't it suffice to have at least 2mb/s? I have 65 on Wi-Fi and 100 on wired connection. If you are using something much higher, you may not notice the flaws.

2mb/s should suffice. I will keep thinking of this. Thanks for testing, and sorry that the problem is not solved yet.

ao-il commented 7 years ago

I think the problem is already solved. Let's not try to reinvent the wheel. There are apps out there that already can stream (over http) without latency problems (gave some examples of some android platform apps), but the question is how is/was it done. I haven't found time myself to research, but there must be some algorithm to handle latency problems, as it is even possible to stream loseless audio. There is also a pulseaudio-dlna (linux) tool that can stream to chromecast, haven't tried it since I'm not a pulseaudio fan, but I believe it does it flawlessly. Thank you too for your efforts.

muammar commented 7 years ago

I have tried pulseaudio-dlna. It works well (I maintain it in Debian), but quality of sound is not the best in my opinion. That's why I decided to do my own :). The difference is that the author of that application did his own implementation of a http server, if I understand correctly. I am using modules already available in Python to do this plus ffmpeg or avconv. I strongly believe that having so many layers also is a possible trigger to all of these latency problems.

I agree with you that the problem is already solved somewhere else. I haven't had either the time to look more into details (started my postdoc 3 weeks ago, ;S).

ao-il commented 7 years ago

I believe so too that having many layers/modules involved contributes to latency. I wish you all the best with your postdoc.

ao-il commented 7 years ago

You probably already know, but there is castnow commandline tool for casting to CC. It is based on node.js which you had in mind earlier. Haven't tried it myself, but it seems to be working for those who have. So maybe replacing python flask with node.js will be the solution?

muammar commented 7 years ago

You probably already know, but there is castnow commandline tool for casting to CC. It is based on node.js which you had in mind earlier. Haven't tried it myself, but it seems to be working for those who have. So maybe replacing python flask with node.js will be the solution?

Yes, I know about them. I don't know how to program using node.js. But mkchromecast for macOS, the mp3 streaming is done with nodejs (I used some packages from node), and it works better (no lag at all). We could try this: https://github.com/fluent-ffmpeg/node-fluent-ffmpeg. I have it in my mind, to give a nodejs option because it is supposed to be better for these things. So yes, that would be a solution for the problem in the case that flask is not working well.

muammar commented 7 years ago

I am testing nodejs to cast video, and the process is smoother.

ao-il commented 7 years ago

Good. Does it work for cc audio as well? Unfortunately, I have replaced my chromecast with a soundcard instead, as I have discovered that the sound output/quality is not identical with the source. Bass and midrange are weak. Maybe ok for casual listening but not for audiophiles. This is probably because it requires faster internet connection, like 250+. I have tried wiring both the chromecast and my computer on my network but it didn't improve. Is it the same with you and how fast is your internet?

muammar commented 7 years ago

Good. Does it work for cc audio as well?

Not yet. But now I am starting to understand node :).

Unfortunately, I have replaced my chromecast with a soundcard instead, as I have discovered that the sound output/quality is not identical with the source. Bass and midrange are weak. Maybe ok for casual listening but not for audiophiles. This is probably because it requires a faster internet connection, like 250+. I have tried wiring both the chromecast and my computer on my network but it didn't improve. Is it the same with you and how fast is your internet?

Oh. Well, when using wav I think that quality is fine. Right now I don't have speakers I had to sell them because I moved out from France to the US.

In France my internet was 50Mbps/20Mbps. I don't think the streaming happens through internet, everything is done withing the local network. I have just tested in the following way: with internet (that is needed to talk to the chromecast, yes, shitty) I started a casting session, and then I disconnected the cable that is coming from my ONT, and the casting process keeps working. I am right now with Verizon Fios which is a symmetric connection of 100/100. But this router is superior to the one I had before and I don't experience any troubles.

ao-il commented 7 years ago

That was what I thought initially, that the sound was crispy, until I compared with a usb sound card. Have you compared the source with the output? My router is netgear CG3100, maybe that needs to be upgraded.

muammar commented 7 years ago

That was what I thought initially, that the sound was crispy, until I compared with a usb sound card. Have you compared the source with the output? My router is netgear CG3100, maybe that needs to be upgraded.

Yes, one looses some quality a would say. I remember I did a test before where I connected directly my computer to my speakers. However, I don't know if it was something related to the sampling rate. I've got issue #40, and OP is using Tidal and he reported back after some exchanges that s/he was getting a decent quality. I don't know if it would apply to your case.

ao-il commented 7 years ago

Sampling should not be the issue, since Google fix that? I learnt that the cc audio and the other devices should be at most 4 meters apart for optimal quality. In my case, I had the cc audio very close to my router and my computer wired 2 meters from the router. I suspect that it's python flask that is responsible, as I could not even stream over Wi-Fi.

ao-il commented 7 years ago

I got the cc audio again and I discovered that ffmpeg was using 24 bit format for wav in the argument list, but I changed it in audio.py and now the sound is better except that the bass is bit thinner than with the USB soundcard. Could this be the nature of the DAC in cc audio or is the signal still not full?

muammar commented 7 years ago

I got the cc audio again and I discovered that FFmpeg was using 24-bit format for wav in the argument list, but I changed it in audio.py and now the sound is better except that the bass is bit thinner than with the USB soundcard.

Good catch. I think that if your audio card is set to have a depth of 16 bit then casting at 24 bit may not be a good idea. I can try to fix that (detect it on Linux). In macOS the history is different because SoundFlower set the audio to 32-bit and sampling down is not an issue.

Could this be the nature of the DAC in cc audio or is the signal still not full?

I can dare to say that it is maybe the nature of the DAS in CC audio, because WAV should be lossless in theory.

ao-il commented 7 years ago

I just discovered a similar tool stream2chromecast, but it seems to have the same problems, which I think are caused by ffmpeg processes. I have tried this: ffmpeg -f alsa -ac 2 -ar 44100 -i hw:1,1 -acodec copy -f wav pipe:1 | stream2chromecast.py -transcodeopts '-acodec copy -f wav' -transcode /dev/stdin

and this: ffmpeg -f alsa -ac 2 -ar 44100 -i hw:1,1 -acodec pcm_s16le -f nut pipe:1 | stream2chromecast.py -transcodeopts '-acodec copy -f wav' -transcode /dev/stdin

Works only with wired connection but hacking on Wi-Fi. Am I doing anything wrong? I have also specified buffer with -transcodebufsize which did not help.

ao-il commented 7 years ago

Ok.. Mystery solved! Tested my throughput and discovered it was much lower than the specs, so I updated the driver of my wireless card, installed some packages, disabled ipv6 (preferred ipv4), and now I can stream on Wi-Fi. In my new setup, the cc device is wired to the router, and with my computer on Wi-Fi, I can stream wav losslessly using stream2chromecast, which I adapted to work without re-encoding the audio. Have not yet tried with mkchromecast, but since it is basically the same processes, it should work too.

muammar commented 7 years ago

Ok.. Mystery solved! Tested my throughput and discovered it was much lower than the specs, so I updated the driver of my wireless card, installed some packages, disabled ipv6 (preferred ipv4), and now I can stream on Wi-Fi. In my new setup, the cc device is wired to the router, and with my computer on Wi-Fi, I can stream wav losslessly using stream2chromecast, which I adapted to work without re-encoding the audio. Have not yet tried with mkchromecast, but since it is basically the same processes, it should work too.

Sorry for the late reply. Yes, it should work with mkchromecast as well. If you can try and let me know, that would be great. That seems to make sense, not reencoding to avoid audio delay. I will tray it later. If you can provide a patch for having such an option, it will be more than welcome :).

ao-il commented 7 years ago

No problem. Yes, it does work with mkchromecast too. For copying the stream, you use 'copy' instead of codec, for ouput. I am not sure how to patch that. Don't understand much of python. However, there is about 3 s delay with copy. Maybe it's because I'm using wav. Copy is basically to ensure lossless audio. You could try to see if it improves for your ogg.

muammar commented 7 years ago

@ao-il thanks for testing!. I will proceed to close this report, and i will implement this option using copy.

muammar commented 7 years ago

@ao-il I remembered you today. I have now a Sonos speaker, and the delay is very short in these ones compared to the chromecasts.

ao-il commented 7 years ago

Thanks for that. But did you do it with mkchromecast or Sonos app? Now, I am working on removing redundancies from the procedure. Firstly, I discovered I didn't need ffmpeg when no transcoding is required. Secondly, with ALSA file device I could just pipe the audio to the python server and then to chromecast (no need for loopback device). This is much better in that I don't need to hardcode the sampling and bit depth parameters i.e bit perfect rendering depending on the playback application. Audacious music player can automatically set bit depth, but it sets 32 for everything and sample rate as is, which to my surprise is accepted by cc audio, which according to specs should only support up to 24/96. I suspect that guy is downsampling the audio, or may it supports 32 and only uses the low bytes up to 3?

muammar commented 7 years ago

Thanks for that. But did you do it with mkchromecast or Sonos app?

With mkchromecast. Delay is < 2 sec.

Now, I am working on removing redundancies from the procedure. Firstly, I discovered I didn't need ffmpeg when no transcoding is required.

You are correct in this. I hadn't thought it on that way.

Secondly, with ALSA file device I could just pipe the audio to the python server and then to chromecast (no need for loopback device). This is much better in that I don't need to hardcode the sampling and bit depth parameters i.e bit perfect rendering depending on the playback application.

How can you do that?.

Audacious music player can automatically set bit depth, but it sets 32 for everything and sample rate as is, which to my surprise is accepted by cc audio, which according to specs should only support up to 24/96. I suspect that guy is downsampling the audio, or may it supports 32 and only uses the low bytes up to 3?

These are the specs of sonos https://sonos.custhelp.com/app/answers/detail/a_id/80/~/supported-audio-formats.

Thanks for sharing all of this information. I think I need to improve these things to reduce delay. I will reopen so that I remember.

ao-il commented 7 years ago

I think Sonos wireless antenna is better than the cc audio's? As regards how to pipe from ALSA file device, that's easy, see the following example:

pcm.!default {
    type plug
    slave.pcm filedev
}
pcm.filedev {
    type file
    format "raw" # or wav in my case
    slave.pcm "hw:0,0" # your card
    file '| stream2chromecast.py -stop && stream2chromecast.py -"
}

then in python server read stdin

      def write_response_stdin(self):
          for line in sys.stdin:
              self.write(chunk_size)
              self.write(...)
              ...

One problem with this approach is that the write buffer underruns due to sampling deviation, thus, the buffer gets reset too quickly before the audio finishes at CC's. My current solution is to pad the audio with silence(-39dB), that would not be removed by silence removal (-40dB) plugin in audacious, and still maintain gapless playback. You may not notice when the sampling parameters are constant, and without gapless playback. To see what is happening in the terminal, use $ tail -f /proc/<python-pid>/fd/1

stuaxo commented 6 years ago

Hi, I found this thread, as I was interested in the latency of sending video (BTW, it looks awesome - so very good work). I get about 5 seconds.

It looks like ffmpeg can serve http itself

https://trac.ffmpeg.org/wiki/ffserver#Connectingyourinputsources

So you might not need flask at all for that bit.

I've got a little bit of experience using python for video based apps, and it is one area where the speed really adds up if it's possible to swap out the ffmpeg->stdout->flask http step and just have ffmpeg handle it, it's got to help a bit.

fedocable commented 6 years ago

Hey there, Muammar. First of all, thanks for the terrific Mkchromecast. After installing it (along with ffmpeg and soundflower) I finally got to stream 24/96 FLAC files from my Mac to the CCA without any distortion. I set the Mkchromecast preferences to WAV and 96khz and everything looks and sounds fine. My only doubt would be: can I be certain that the files are actually playing at the correct bit & sample rate? Or could there be some downsampling in the process? Unfortunately I have no way to find out in my system (CCA is connected directly to a tube amp). They sure sound good, but it's hard to tell the difference between 44 and 96khz using just my ears. (I know: then why do I care? Well, as you know these forums are full of OCDs...)

muammar commented 6 years ago

Hey there, Muammar. First of all, thanks for the terrific Mkchromecast.

Thanks!

After installing it (along with ffmpeg and soundflower) I finally got to stream 24/96 FLAC files from my Mac to the CCA without any distortion. I set the Mkchromecast preferences to WAV and 96khz and everything looks and sounds fine. My only doubt would be: can I be certain that the files are actually playing at the correct bit & sample rate? Or could there be some downsampling in the process? Unfortunately I have no way to find out in my system (CCA is connected directly to a tube amp). They sure sound good, but it's hard to tell the difference between 44 and 96khz using just my ears. (I know: then why do I care? Well, as you know these forums are full of OCDs...)

Did you read https://github.com/muammar/mkchromecast/issues/40? We were able to make the output from ffmpeg to respect the bitrate and sample rate. I don't know how to check what the chromecast is playing tough. I set the mtype according to the codec that is being used and the raw output from ffmpeg is piped to the webserver. Then, the chromecast is reading that stream.

muammar commented 4 years ago

See: https://github.com/muammar/mkchromecast/issues/292