首先声明这个功能是用纯粹的socket实现的,这个协议实在是很简单,想了解底层原理的可以参考下。
电视是小米电视。
我只实现了投屏播放,什么快进、暂停很多功能都懒得的实现,其他功能也可以参考下面文章(我只看了协议部分,代码完全没参考,也没看),其他功能原理都是一样的,你会播放那么暂停不是很简单吗。
dlna协议说明
下面我就不大谈特谈原理了,首先你投屏到电视,你得知道电视的ip地址和信息吧,所以第一步就是用udp组播发现设备,地址很简单:239.255.255.250:1900,这是个组播地址,ip是239.255.255.250,端口是1900,看到这个是不是很激动,很想马上尝试写个udp发送一个数据包,看返回的是什么,别急你发送的数据包得符合协议标准,你得发送下面这串文本内容,看到这个字符串是不是感觉有点像http协议内容,只能说有点像,不过后面发送控制请求就是http协议了,有网络编程经验的人可能猜到,http协议不是用tcp实现的吗,确实没错,dlna协议发现设备用的是udp,控制设备用的是tcp。
udp发送协议包,就是串字符串哈。这里有个坑,有http经验的人发送数据包后,没有收到数据,可能会搞定,如果没有也没关系。这个坑就是每行后面都要加\r\n,而且最后一行要加\r\n\r\n,有可能会返回几条,比如本人就收到两条,一条是路由器的,路由器的不用管。
数据包协议格式:
M-SEARCH * HTTP/1.1
ST: upnp:rootdevice
HOST: 239.255.255.250:1900
MX: 3
MAN: "ssdp:discover"
发送udp数据包:
UdpClient udpClient = new UdpClient();
byte[] s_data = Encoding.UTF8.GetBytes("M-SEARCH * HTTP/1.1\r\nST: upnp:rootdevice\r\nHOST: 239.255.255.250:1900\r\nMX: 3\r\nMAN: \"ssdp:discover\"\r\n\r\n");
udpClient.Send(s_data, s_data.Length, new IPEndPoint(IPAddress.Parse("239.255.255.250"), 1900));
IPEndPoint iPEndPoint = null;
while (true) {
byte[] data = udpClient.Receive(ref iPEndPoint);
Console.WriteLine(Encoding.UTF8.GetString(data));
}
发送过udp数据包,你能收到数据包,说明已经成功了,是不是很激动、很简单,就是如此简单,此时你已经能和电视通信了,下面就要控制电视播放视频,如果你和我一样是小米电视,那就再好不过了,其他品牌电视应该也差不多,都是基于dlna协议的。
返回的数据包格式:
HTTP/1.1 200 OK
CACHE-CONTROL: max-age=66
DATE: Sat, 24 Feb 2024 08:01:20 GMT
EXT:
LOCATION: http://192.168.0.103:49152/description.xml
OPT: "http://schemas.upnp.org/upnp/1/0/"; ns=01
01-NLS: 8ee64ea2-1dd2-11b2-82c1-d5f8451be6aa
SERVER: Linux/4.9.113 HTTP/1.0
X-User-Agent: redsonic
ST: upnp:rootdevice
USN: uuid:5bbf0462-01c7-16d3-7d81-3fa5cb113532::upnp:rootdevice
其他的都不需要关心,只需要关心这个LOCATION: http://192.168.0.103:49152/description.xml,电视ip是192.168.0.103,端口是49152。
控制代码:是采用tcp
string s2 = File.ReadAllText("2.txt");
string s1 = $"POST /_urn:schemas-upnp-org:service:AVTransport_control HTTP/1.1\r\nConnection: close\r\nSOAPACTION: \"urn:schemas-upnp-org:service:AVTransport:1#SetAVTransportURI\"\r\nContent-Type: text/xml;charset=\"utf-8\"\r\nContent-Length: {Encoding.UTF8.GetBytes(s2).Length}\r\nHost: 192.168.0.103:49152\r\nUser-Agent:\r\n\r\n";
string s = s1 + s2;
Console.WriteLine(s);
Console.WriteLine();
TcpClient tcpClient = new TcpClient();
tcpClient.Connect(new IPEndPoint(IPAddress.Parse("192.168.0.103"), 49152));
var stream = tcpClient.GetStream();
stream.Write(Encoding.UTF8.GetBytes(s), 0, Encoding.UTF8.GetBytes(s).Length);
byte[] data = new byte[4096];
int len = stream.Read(data, 0, 4096);
stream.Close();
tcpClient.Close();
Console.WriteLine(Encoding.UTF8.GetString(data, 0, len));
s1字符串中的Host: 192.168.0.103:49152中的192.168.0.103:49152替换掉你电视的ip和端口,这个内容的获取前面说过,是发现设备返回的。
上面s2我用了文件,因为拼接字符串太坑爹,下面是2.txt文件的内容:
2.txt
<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:SetAVTransportURI xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
<InstanceID>0</InstanceID>
<CurrentURI>http://192.168.0.105/1.mp4</CurrentURI>
<CurrentURIMetaData></CurrentURIMetaData>
</u:SetAVTransportURI>
</s:Body>
</s:Envelope>
CurrentURI中的http://192.168.0.105/1.mp4替换为你的视频地址,这视频地址可以是你http服务的地址,我用的是iis,你可以用nginx或其他的http服务器,实在没有可以用视频网站的视频地址。
现在发送控制协议就能播放视频了,如果不能可以加入控制播放的action(请参考最上面的协议说明),反正我的电视就直接播放了, 还是不行有可能是转义问题,也有可能是你的电视要CurrentURIMetaData这里的地址,请参考我最开头的协议说明,另外说下这篇文章说的转义,你可以把示例CurrentURI、CurrentURIMetaData里面的内容进行反转义,CurrentURIMetaData需要反转义2次,转义工具百度搜索“转义在线”,另外其他功能请参考这篇文章。
最后我说下电脑录屏带声音投屏到电视,这里有2个解决方法。
1,用ffmpeg,但是录制声音就很麻烦,请自行解决。
.\ffmpeg -f gdigrab -framerate 25 -i desktop -preset ultrafast -tune zerolatency -pix_fmt yuv420p -c:v libx264 -threads 0 -f mpegts udp://192.168.0.103:8888
udp://192.168.0.103:8888只需替换ip为你电视的即可,端口随意只要不冲突。
2,最好的解决办法是用obs(带声音),先把2.txt的CurrentURI地址http://192.168.0.105/1.mp4直播地址写udp://192.168.0.103:8888即可,注意替换ip为你电视的即可,如果不会自己百度,搞开发的这点不会真是说不过去。
现在还有一个问题是有些电视自带的dlna协议不支持udp视频流,反正我小米的就不支持,怎么办呢?解决办法是安装乐播投屏。乐播投屏还有一个问题是,有时候能正确播放有时不能,解决办法是切换视频编码,可以都试下。
建议先运行obs,再运行你的程序。