我不知道你是否解决了你的问题,但我有同样的问题,我设法使它以某种方式与C++工作。我花了一些时间来弄清楚我必须做什么,我从来没有做过任何HTTP的东西,甚至没有开发出即插即用的驱动程序,所以我会解释我是如何一步一步做的,因为我希望我已经解释过了。
在信息的末尾,我给出了一个链接到我的整个文件,随时尝试它。
我为每个网络相关的问题使用boost asio库,以及更多(一切异步,真的,这是一个伟大的库,但很难理解像我这样的无知的人......)。我的大部分函数都是从文档中的示例部分复制粘贴的,这就解释了为什么我的代码在某些地方很笨拙。这里是我的主要功能,没有什么花哨我实例化一个ASIO :: io_service对象,创建我的对象(即我错误命名multicast_manager),然后运行该服务:
#include <bunch_of_stuff>
using namespace std;
namespace basio = boost::asio;
int main(int argc, char* argv[]) {
try {
basio::io_service io_service;
multicast_manager m(io_service, basio::ip::address::from_string("239.255.255.250"));
io_service.run();
m.parse_description();
m.start_liveview();
io_service.reset();
io_service.run();
m.get_live_image();
io_service.reset();
io_service.run();
} catch (const std::exception& e) {
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
发现相机上的SSDP
首先,我们必须使用upnp(universal plug and play)功能连接到相机。原则是每个upnp设备正在侦听M-SEARCH请求的多播端口230.255.255.250:1900。这意味着如果您将正确的信息发送到此地址,设备将通过告诉您它是否存在来回答,并为您提供使用信息。文档中给出了正确的信息。我遇到了两个陷阱:首先,我省略了在消息结尾添加换行符,如the http standard中所述。所以,你要发送的消息可以是建立这样的:
multicast_manager(basio::io_service& io_service, const basio::ip::address& multicast_address)
: endpoint_(multicast_address, 1900),
socket_(io_service, endpoint_.protocol())
{
stringstream os;
os << "M-SEARCH * HTTP/1.1\r\n";
os << "HOST: 239.255.255.250:1900\r\n";
os << "MAN: \"ssdp:discover\"\r\n";
os << "MX: 4\r\n";
os << "ST: urn:schemas-sony-com:service:ScalarWebAPI:1\r\n";
os << "\r\n";
message_ = os.str();
// ...
第二件事,在这部分重要的是检查该消息被发送到正确的网络接口。在我的情况下,即使它被禁用,但走了出去,通过我的以太网卡,直到我在插座改变了正确的选择,我解决了这个问题,用下面的代码:
// ...
socket_.set_option(basio::ip::multicast::outbound_interface(
basio::ip::address_v4::from_string("10.0.1.1")));
socket_.async_send_to(
basio::buffer(message_), endpoint_,
boost::bind(&multicast_manager::handle_send_to, this,
basio::placeholders::error));
}
现在我们听。我们倾听你可能会问的问题,你是否像我一样?什么端口,什么地址?那么,我们不关心:事情是,当我们发送消息时,我们定义了一个目标ip和端口(在端点构造函数中)。我们并不一定要定义任何本地地址,它是我们自己的ip地址(其实我们确实是定义它,但只是为了知道哪个网络接口可以选择);并且我们没有定义任何本地端口,它实际上是自动选择的(我猜是通过操作系统?)。无论如何,重要的一点是任何听到多播组的人都会收到我们的消息并知道它的来源,并且会直接响应正确的IP和端口。因此,没有必要在这里说明什么,没有必要建立一个新的socket,我们只是听我们在一个瓶子发送我们的信息相同的插座:
void handle_send_to(const boost::system::error_code& error)
{
if (!error) {
socket_.async_receive(asio::buffer(data_),
boost::bind(&multicast_manager::handle_read_header, this,
basio::placeholders::error,
basio::placeholders::bytes_transferred));
}
}
如果一切发展顺利,答案沿云行:
HTTP/1.1 200 OK
CACHE-CONTROL: max-age=1800
EXT:
LOCATION: http://10.0.0.1:64321/DmsRmtDesc.xml
SERVER: UPnP/1.0 SonyImagingDevice/1.0
ST: urn:schemas-sony-com:service:ScalarWebAPI:1
USN: uuid:00000000-0005-0010-8000-10a5d09bbeda::urn:schemas-sony-com:service:ScalarWebAPI:1
X-AV-Physical-Unit-Info: pa=""; pl=;
X-AV-Server-Info: av=5.0; hn=""; cn="Sony Corporation"; mn="SonyImagingDevice"; mv="1.0";
解析这个消息,我再次使用解析从boost http client example,除了我一气呵成这样做是因为出于某种原因,我不能和UDP套接字做一个async_read_until。无论如何,重要的部分是相机收到我们的信息;另一个重要部分是描述文件DmsRmtDesc.xml的位置。
检索和阅读的描述文件
我们需要得到DmsRmtDesc.xml。这次我们将直接发送一个GET请求到相机,在指定的IP地址和端口。此请求类似于:
GET /DmsRmtDesc.xml HTTP/1.1
Host: 10.0.0.1
Accept: */*
Connection: close
不要忘记多余的空行。我不知道连接是什么:close意味着什么。 accept线指定您接受的答案的应用程序类型,在这里我们将采取任何答案。我使用boost http客户端的例子得到了这个文件,基本上我打开一个socket到10.0.0.1:64321并接收HTPP头文件,然后是文件内容。现在我们有一个xml文件,其中包含我们要使用的web服务的地址。让我们再分析它使用升压,我们要检索的摄像机服务地址,也许实时取景的流地址:
namespace bpt = boost::property_tree;
bpt::ptree pt;
bpt::read_xml(content, pt);
liveview_url = pt.get<string>("root.device.av:X_ScalarWebAPI_DeviceInfo.av:X_ScalarWebAPI_ImagingDevice.av:X_ScalarWebAPI_LiveView_URL");
for (bpt::ptree::value_type &v : pt.get_child("root.device.av:X_ScalarWebAPI_DeviceInfo.av:X_ScalarWebAPI_ServiceList")) {
string service = v.second.get<string>("av:X_ScalarWebAPI_ServiceType");
if (service == "camera")
camera_service_url = v.second.get<string>("av:X_ScalarWebAPI_ActionList_URL");
}
一旦做到这一点,我们就可以开始发送实际的命令到相机,并使用API。
发送命令到相机
的想法很简单,我们使用文档中提供的JSON格式建立我们的命令,我们用POST HTTP请求到相机服务发送。我们将推出实时取景模式,所以我们发送POST请求(我们最终将不得不使用升压property_tree建立我们的JSON字符串,在这里我手工做的):
POST /sony/camera HTTP/1.1
Accept: application/json-rpc
Content-Length: 70
Content-Type: application/json-rpc
Host:http://10.0.0.1:10000/sony
{"method": "startLiveview","params" : [],"id" : 1,"version" : "1.0"}
我们把它发送到10.0.0.1: 10000,等待回答:
HTTP/1.1 200 OK
Connection: close
Content-Length: 119
Content-Type: application/json
{"id":1,"result":["http://10.0.0.1:60152/liveview.JPG?%211234%21http%2dget%3a%2a%3aimage%2fjpeg%3a%2a%21%21%21%21%21"]}
我们得到实时查看网址第二次,我不知道哪一个更好,它们是相同的...
无论如何,现在我们知道如何向摄像机发送命令并检索其答案,我们仍然需要获取图像流。
从实时取景流
抓取的图像我们有实时取景的网址,我们有API的参考指南中的规范。第一件事,第一,我们要求相机向我们发送流,所以我们发送GET请求来10.0.0.1:60152:
GET /liveview.JPG?%211234%21http%2dget%3a%2a%3aimage%2fjpeg%3a%2a%21%21%21%21%21 HTTP/1.1
Accept: image/jpeg
Host: 10.0.0.1
我们等待答案,不应该长时间。答案开始与通常HTTTP头:
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Pragma: no-cache
CACHE-CONTROL: no-cache
Content-Type: image/jpeg
transferMode.dlna.org: Interactive
Connection: Keep-Alive
Date: Wed, 09 Jul 2014 14:13:13 GMT
Server: UPnP/1.0 SonyImagingDevice/1.0
根据文档,这应该是直接跟着实时取景的数据流至极在于理论:公共报头指定如果
- 8字节我们确实处于实时查看模式。
- 128字节的有效载荷数据给出jpg数据的大小。
- n个字节的jpeg数据。
然后我们再次获得公共标题,直到我们关闭套接字为止。
在我的情况下,通用头文件以“88 \ r \ n”开头,所以我不得不放弃它,并且在切换到下一帧之前,jpg数据后面跟着10个额外字节,所以我不得不采取考虑到。我还必须自动检测jpg图像的开始,因为jpg数据以包含我忽略的数字的文本开始。很可能这些错误是由于我做错了一些事情,或者我不了解我在这里使用的技术。
我的代码现在正常工作,但最后的位非常特别,它肯定需要一些更好的检查。
它也需要大量的重构是可用的,但它显示了每个步骤的运作,我猜...
Here is the entire file if you want to try it out. And here is a working VS project on github.
我刚刚下载的API和所有它包含的是REST文档和示例使用它的android应用程序。因此,只要遵循其REST规范,操作系统似乎并不重要。你试过了吗? – Jon
感谢您的信息。不,我还没有尝试过。我打算很快潜入,但在比较图中被这个音符推迟了。我很想听听您是否在Windows上取得成功。 –