I just managed to save the frames in files and wrote a small python script to 
display them (webcamdata.py).
Please note that this is my first C++ project so it is really buggy and will 
likely need a complete rewrite.

The current features are:
 - Webcam inventations can be accepted
 - Webcam frames are decoded using libmimic
 - The decoded frames are saved in the folder /tmp/webcamdata
 - There is a small script to display these frames

The drawbacks are:
 - Only receiving is implemented
 - I think it will only work if both clients are in the same network 
   (There is currently no possibilty to get the external IP address)
 - There can be only one session
 - The webcam socket in msntest blocks the whole application
   (if linked to QtNetwork it would be possible to use a QTcpServer)

Lukas

Am Freitag 21 Mai 2010 21:47:38 schrieb Lukas Hetzenecker:
> Hello to all libmsn and kopete developers,
> 
> I was trying to add support for webcam conversations to libmsn and have
> some questions regarding this topic.
> 
> At first I wanted to ask if somebody knows the current state of the
> telepathy plugin for kopete (as seen in the private conversation to Tiago
> below). The msn connection manager telepathy-butterfly is based upon
> papyon, a python library for the MSN Messenger network. Papyon would
> already support webcam conversations using the "webcam protocol" and SIP
> (which currently seems to be broken, maybe due to the same issues as amsn
> [1]).
> 
> Libmsn had already initial support for the handshake and I tried to
> implement the other parts, but I'm currently stuck.
> I got the XML description for the webcam transfer from the other client but
> don't know what to respond exactly. I tried to follow the payon code as
> close as possible, but couldn't manage to get it work. I also couldn't
> find any useful information in the internet and sniffing data from amsn
> also didn't help. Could anybody explain the handshake to me?
> 
> I attached my current diff file, but it needs a major refactoring before
> the release.  The biggest changes are some debug lines that I used to
> understand the protocol. I think the best way to implement the webcam P2P
> protocol would be to create P2P as base class and FileTransfer and Webcam
> as subclasses. Does anybody have a issue with the proposed method?
> 
> Greetings,
> Lukas
> 
> [1] 
> http://kakaroto.homelinux.net/2010/03/amsn-0-98-2-to-be-released-without-
> audiovideo-support/
> 
> Am Freitag 14 Mai 2010 20:17:19 schrieb Tiago Salem Herrmann:
> > Hi, thanks for the contact.
> > 
> > On Thu, May 13, 2010 at 5:31 PM, Lukas Hetzenecker <l...@gmx.at> wrote:
> > > Hello Tiago and Will,
> > > 
> > > This project would certainly have been interesting, it's a pity that it
> > > wasn't accepted.
> > > Webcam support in WLM is requested by many people [see bug 70538 for
> > > example] and would be the killer feature in kopete, because many other
> > > clients can't handle it.
> > 
> > Indeed.
> > 
> > > At the beginng I have some questions about the future developement of
> > > kopete: - What is the current state of the telepathy plugin?
> > > I've read that the Telepathy framework uses farsight to handle media
> > > streams [1], so would it be better to use the Telepathy-Qt4 library [2]
> > > directly?
> > > 
> > > There are many different protocols for webcam / video conversations as
> > > described here [3].
> > > The most important seem to be the "The Computer Call" and "The Webcam":
> > > 
> > > The Webcam uses the ML20 codec for encoding and seems to be rather easy
> > > to implement. I've used wireshark to sniff for some traffic generated
> > > by amsn. If you want I can send you some tcpdump capture files.
> > > libmimic could be used to decode the video stream (I don't think
> > > farsight would be a good idea to use directly, because it needs Glib
> > > and uses GObjects).
> > > There are already implementations in amsn [4][5] and papyon [6].
> > > 
> > > The official Live Messenger seems to use the "Computer Call" whenever
> > > it is possible. This protocol is based on SIP and RTP. It also
> > > requires MSNP2PV2 and therefore MSNP18. Porting to this version is the
> > > ideal long-term goal, because if also offers many other features like
> > > "Multiple Points of Presence" and Group sessions [7]. Also amsn will
> > > go this way soon [8].
> > > 
> > > I would be glad to help in the developement of libmsn and kopete.
> > 
> > Nice, so do you know exactly what was changed from MSNp15 to MSNp18?
> > It's been a while since I stopped following the msn protocol updates.
> > I believe that updating libmsn to a new protocol would be a very good
> > idea.
> > 
> > > Greetings from Austria,
> > > Lukas Hetzenecker
> > > 
> > > p.s. Would you mind if i CC kopete-devel and Libmsn-discuss too?
> > 
> > Not at all. Please do it.
> > 
> > > [1] http://farsight.freedesktop.org/wiki/
> > > [2] http://telepathy.freedesktop.org/wiki/Components
> > > [3] http://imfreedom.org/wiki/MSN:AV
> > > [4]
> > > http://amsn.svn.sourceforge.net/viewvc/amsn/trunk/amsn/msncam.tcl?view=
> > > ma rkup [5]
> > > http://amsn.svn.sourceforge.net/viewvc/amsn/trunk/amsn/msnp2p.tcl?view=
> > > ma rkup [6]
> > > http://git.collabora.co.uk/?p=papyon.git;a=blob;f=papyon/msnp2p/webcam.
> > > p y [7]
> > > http://en.wikipedia.org/wiki/Microsoft_Notification_Protocol#MSNP17
> > > [8]
> > > http://kakaroto.homelinux.net/2010/03/amsn-0-98-2-to-be-released-withou
> > > t - audiovideo-support/
> > > 
> > > Am Mittwoch 12 Mai 2010 17:32:16 schrieb Will Stephenson:
> > >> Tiago:  LuHe was asking in Kopete about MSN webcam so I am cc'ing him
> > >> your proposal outline - perhaps you could join forces.
> > >> 
> > >> Will
> > >> 
> > >> 
> > >> "Since KDE 4.2, kopete is using WLM (based on libmsn [2]) as the main
> > >> MSN plugin. WLM is working fine so far, but there is a big feature
> > >> missing: Webcam support. This feature will require some work both on
> > >> WLM and libmsn. Libmsn still does not recognize the webcam protocol,
> > >> only the initial handshake. The webcam communication is based on a P2P
> > >> protocol and unfortunately it is not well documented, not even in
> > >> msnpiki [5]. Currently there is an opensource library called libmimic
> > >> that helps on encoding/decoding the stream sent/received by the
> > >> official client. Part of the work will be link libmimic to libmsn (or
> > >> WLM, as it will be decided during the development).
> > >> 
> > >> WLM will also need a new UI to manage the webcam calls. This UI will
> > >> be created with qtdesigner and (if possible) fully integrated with
> > >> the chat window. The current implementation shows the webcam calls
> > >> out of the chatwindow, and this way it is not easy to track which
> > >> webcam call belong to a certain chatwindow. Moving the webcam window
> > >> to inside the chatwindow will improve the user experience."

Index: msn/p2p.cpp
===================================================================
--- msn/p2p.cpp	(Revision 120)
+++ msn/p2p.cpp	(Arbeitskopie)
@@ -20,6 +20,8 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
+#include <msn/debug.h>
+
 #include <msn/notificationserver.h>
 #include <msn/errorcodes.h>
 #include <msn/externals.h>
@@ -32,13 +34,16 @@
 #include <fstream>
 #include <vector>
 
-#include <string.h>
+#include <cstring>
 
+#include <mimic.h>
+
 namespace MSN {
 
     P2P::P2P()
     {
         rand_helper = 1;
+
     }
     P2P::~P2P()
     {
@@ -51,7 +56,11 @@
         p2pPacket packet;
         // not for me, ignore it
         if(headers["P2P-Dest"] != conn.myNotificationServer()->myPassport)
+        {
+            std::cout << "not my dest!" << std::endl;
+            std::flush(std::cout);
             return;
+        }
 
         std::istringstream header(body,std::ios::binary);
 
@@ -73,10 +82,29 @@
         
         if(packet.p2pHeader.flag==FLAG_ACK)
         {
+            debug_start();
+            debug_line("Handle ack to P2P package " << packet.p2pHeader.ackID);
+            debug_end();
             handle_p2pACK(conn, packet);
             return;
         }
 
+        debug_start();
+        debug_line("Got P2P message!");
+        debug_line("Session ID:      " << packet.p2pHeader.sessionID);
+        debug_line("Identifier:      " << packet.p2pHeader.identifier);
+        debug_line("Data offset:     " << packet.p2pHeader.dataOffset);
+        debug_line("Total data size: " << packet.p2pHeader.totalDataSize);
+        debug_line("Message Length:  " << packet.p2pHeader.messageLength);
+        debug_line("Flag:            " << packet.p2pHeader.flag);
+        debug_line("Ack ID:          " << packet.p2pHeader.ackID);
+        debug_line("Ack UID:         " << packet.p2pHeader.ackUID);
+        debug_line("Ack Data size:   " << packet.p2pHeader.ackDataSize);
+        debug_line("Body:");
+        debug_line(packet.body);
+        debug_line("Footer:          " << packet.p2pFooter.appID);
+        debug_end();
+
         if(packet.p2pHeader.sessionID == 0x40 &&
                 little2big_endian(packet.p2pFooter.appID)==3) // INK, oh god!
         {
@@ -134,17 +162,24 @@
         }
 
         // in these conditions, the packet is data, receive it to a file
-        if(packet.p2pHeader.sessionID &&
-               (packet.p2pHeader.flag == FLAG_FILE_DATA ||
-                packet.p2pHeader.flag == FLAG_FILE_DATA2 ||
-                packet.p2pHeader.flag == FLAG_DATA_EMOTICONS)) 
+        if(packet.p2pHeader.sessionID)
         {
             // we need to ensure we have a started session
             if(!startedSessions.count(packet.p2pHeader.sessionID))
                 return;
+            startedSessions[packet.p2pHeader.sessionID].step = STEP_RECEIVING;
 
-            startedSessions[packet.p2pHeader.sessionID].step = STEP_RECEIVING;
-            receiveP2PData(conn,packet);
+            if (packet.p2pHeader.flag == FLAG_FILE_DATA ||
+                packet.p2pHeader.flag == FLAG_FILE_DATA2 ||
+                packet.p2pHeader.flag == FLAG_DATA_EMOTICONS)
+            {
+                receiveP2PData(conn,packet);
+            }
+            else if (packet.p2pHeader.flag == FLAG_WEBCAM)
+            {
+                receiveP2PWebcamData(conn,packet);
+            }
+
             return;
         }
 
@@ -215,7 +250,8 @@
         {
             handle_603Decline(conn,packet);
         }
-/*        std::cout << "session id: " << packet.p2pHeader.sessionID << std::endl;
+        /*std::cout << "------------------" << std::endl;
+        std::cout << "session id: " << packet.p2pHeader.sessionID << std::endl;
         std::cout << "identifier: " << packet.p2pHeader.identifier << std::endl;
         std::cout << "dataOffset: " << packet.p2pHeader.dataOffset << std::endl;
         std::cout << "totalDataSize: " << packet.p2pHeader.totalDataSize << std::endl;
@@ -225,7 +261,9 @@
         std::cout << "ackUID: " << packet.p2pHeader.ackUID << std::endl;
         std::cout << "ackDataSize: " << packet.p2pHeader.ackDataSize << std::endl;
         std::cout << "footer: " << packet.p2pFooter.appID << std::endl << std::endl;
-*/
+        std::cout << "------------------" << std::endl;
+        std::flush(std::cout);*/
+
     }
 
     void P2P::sendACK(MSN::SwitchboardServerConnection &conn, p2pPacket &packet, p2pSession &session)
@@ -276,6 +314,26 @@
         buf_ << "MSG " << conn.trID++ << " D " << full_msg.str().size() << "\r\n";
         buf_ << full_msg.str();
 
+        debug_start();
+        debug_line("Send ACK to package id " << ack_pkt.p2pHeader.ackID);
+        debug_end();
+
+        /*debug_start();
+        debug_line("Send ACK!");
+        debug_line("Session ID:      " << ack_pkt.p2pHeader.sessionID);
+        debug_line("Identifier:      " << ack_pkt.p2pHeader.identifier);
+        debug_line("Data offset:     " << ack_pkt.p2pHeader.dataOffset);
+        debug_line("Total data size: " << ack_pkt.p2pHeader.totalDataSize);
+        debug_line("Message Length:  " << ack_pkt.p2pHeader.messageLength);
+        debug_line("Flag:            " << ack_pkt.p2pHeader.flag);
+        debug_line("Ack ID:          " << ack_pkt.p2pHeader.ackID);
+        debug_line("Ack UID:         " << ack_pkt.p2pHeader.ackUID);
+        debug_line("Ack Data size:   " << ack_pkt.p2pHeader.ackDataSize);
+        debug_line("Body:");
+        debug_line(packet.body);
+        debug_line("Footer:          " << ack_pkt.p2pFooter.appID);
+        debug_end();*/
+
         if (conn.write(buf_) != buf_.str().size())
             return;
 /*            std::cout << "session id: " << ack_pkt.p2pHeader.sessionID << std::endl;
@@ -294,6 +352,9 @@
 
     void P2P::receiveP2PData(MSN::SwitchboardServerConnection &conn, p2pPacket &packet)
     {
+        std::cout << "got data from session " << packet.p2pHeader.sessionID << std::endl;
+        std::flush(std::cout);
+
         // check if there is no session
         if(!startedSessions.count(packet.p2pHeader.sessionID))
             return;
@@ -356,6 +417,194 @@
         }
     }
 
+    void P2P::receiveP2PWebcamData(MSN::SwitchboardServerConnection &conn, p2pPacket &packet)
+    {
+        static std::string data = "";
+        static int s = 0;
+        std::cout << "got webcam data from session " << packet.p2pHeader.sessionID << std::endl;
+        std::flush(std::cout);
+
+        // check if there is no session
+        if(!startedSessions.count(packet.p2pHeader.sessionID))
+            return;
+
+        p2pSession session = startedSessions[packet.p2pHeader.sessionID];
+
+        // we have to wait until the message is complete...
+        data.insert(packet.p2pHeader.dataOffset, packet.body);
+        if (packet.p2pHeader.totalDataSize != data.length())
+            return;
+
+        // use _ucs2_utf8
+        std::string content = "";
+        for(int i = 5; i <= data.length() / 2; i++) // skip the first 10 bytes and return every second
+        {
+            if (data.at(i*2) == '\0')
+                break;
+            content.append(&data.at(i*2));
+        }
+
+        std::cout << "content is: " << content << " (" << content.length() << ")" << std::endl;
+        std::cout << "producer: " << content.find("<producer>") << "; npos: " << std::string::npos << std::endl;
+        std::flush(std::cout);
+
+        if (content == "syn")
+        {
+            std::cout << "send ack" << std::endl;
+            std::flush(std::cout);
+
+            session.currentIdentifier++;
+            if(session.currentIdentifier == session.baseIdentifier) // skip the original identifier
+                session.currentIdentifier++;
+
+            p2pPacket pkt;
+            pkt.p2pHeader.sessionID = packet.p2pHeader.sessionID;
+            pkt.p2pHeader.flag = FLAG_NOP;
+            pkt.p2pHeader.identifier = session.currentIdentifier;
+            pkt.p2pHeader.ackID = rand()%0x8FFFFFF0 + rand_helper++;
+            pkt.p2pHeader.ackUID = 0;
+            pkt.p2pHeader.ackDataSize = 0;
+            // big endian
+            pkt.p2pFooter.appID = little2big_endian(session.appID);
+
+            std::ostringstream content;
+            //content.write("a\00c\00k\00\00\00",8);
+            content.write("\x80\xEA\x00\x00\x08\x00\x08\x00\x00\x00\x61\x00\x63\x00\x6b\x00\x00\x00",18);
+
+            pkt.body = content.str();
+
+            sendP2PPacket(conn, pkt, session);
+        }
+        else if (content.find("<producer>") != std::string::npos)
+        {
+            std::cout << "start parsing" << std::endl;
+            std::flush(std::cout);
+
+            XMLNode producer = XMLNode::parseString(content.c_str(), "producer");
+            //std::string version = producer.getChildNode("version").getText();
+            //std::string rid = producer.getChildNode("rid").getText();
+
+            /*
+            XMLNode viewer = XMLNode::createXMLTopNode("viewer");
+            viewer.addChild("version").addText("2.0");
+            viewer.addChild("rid").addText(producer.getChildNode("rid").getText());
+            viewer.addChild("session").addText(producer.getChildNode("session").getText());
+            viewer.addChild("ctypes").addText("0");
+            viewer.addChild("cpu").addText("2010");
+            XMLNode tcp = viewer.addChild("tcp");
+            tcp.addChild("tcpport").addText(producer.getChildNode("tcp").getChildNode("tcpport").getText());
+            tcp.addChild("tcplocalport").addText(producer.getChildNode("tcp").getChildNode("tcpport").getText());
+            tcp.addChild("tcpexternalport").addText("0");
+
+            int u = 0;
+            for(int i = 0; i <= producer.getChildNode("tcp").nChildNode(); i++)
+            {
+                std::string name = producer.getChildNode(i).getName();
+                if (name.find("tcpipaddress") != std::string::npos)
+                {
+                    tcp.addChild("tcpipaddress" + u).addText(producer.getChildNode(i).getText());
+                    u++;
+                }
+            }
+
+            viewer.addChild("codec").addText("");
+            viewer.addChild("channelmode").addText("2");
+            */
+
+            /*
+            XMLNode viewer = XMLNode::createXMLTopNode("viewer");
+            viewer.addChild("version").addText("2.0");
+            viewer.addChild("rid").addText("108");
+            viewer.addChild("session").addText(producer.getChildNode("session").getText());
+            viewer.addChild("ctypes").addText("0");
+            viewer.addChild("cpu").addText("730");
+            XMLNode tcp = viewer.addChild("tcp");
+            tcp.addChild("tcpport").addText("6891");
+            tcp.addChild("tcplocalport").addText("6891");
+            tcp.addChild("tcpexternalport").addText("6891");
+            tcp.addChild("tcpipaddress1").addText(producer.getChildNode("tcp").getChildNode("tcpipaddress2").getText());
+            tcp.addChild("tcpipaddress2").addText("192.168.1.101");
+
+            viewer.addChild("codec").addText("");
+            viewer.addChild("channelmode").addText("1");
+            */
+
+            //std::string xml = viewer.createXMLString();
+            std::string xml = "<viewer>";
+            xml += "<version>2.0</version>";
+            xml += "<rid>143</rid>";
+            xml += "<session>" + std::string(producer.getChildNode("session").getText()) + "</session>";
+            xml += "<ctypes>0</ctypes>";
+            xml += "<cpu>730</cpu>";
+            xml += "<tcp>";
+            xml += "<tcpport>6891</tcpport>";
+            xml += "        <tcplocalport>6891</tcplocalport>";
+            xml += "        <tcpexternalport>6891</tcpexternalport>";
+            xml += "<tcpipaddress1>" + std::string(producer.getChildNode("tcp").getChildNode("tcpipaddress2").getText()) + "</tcpipaddress1>";
+            xml += "<tcpipaddress2>" + conn.myNotificationServer()->externalCallbacks.getOurIP()  + "</tcpipaddress2>";
+            xml += "</tcp>";
+            xml += "<codec></codec>";
+            xml += "<channelmode>1</channelmode>";
+            xml += "</viewer>";
+
+            std::cout << "send: " << xml << std::endl;
+            std::flush(std::cout);
+
+            std::string temp;
+            for (unsigned int i = 0; i < xml.length(); i++)
+            {
+                //if (xml[i] != ' ' && xml[i] != '\n' && xml[i] != '\t')
+                if (xml[i] == ' ')
+                    temp += '\t';
+                if (xml[i] != '\n')
+                    temp += xml[i];
+            }
+            xml = temp;
+
+            // TODO - convert filename to ucs2
+            U8 *filenameutf8 = new U8[800];
+            U8 *filenameutf16 = new U8[801];
+            memset(filenameutf8, 0, 800);
+            memset(filenameutf16, 0, 801);
+            memcpy(filenameutf8, xml.c_str(), xml.size());
+            _utf8_ucs2(filenameutf16, filenameutf8);
+            filenameutf16++;
+
+            session.currentIdentifier++;
+            if(session.currentIdentifier == session.baseIdentifier) // skip the original identifier
+                session.currentIdentifier++;
+
+            p2pPacket pkt;
+            pkt.p2pHeader.sessionID = packet.p2pHeader.sessionID;
+            pkt.p2pHeader.flag = FLAG_NOP;
+            pkt.p2pHeader.identifier = session.currentIdentifier;
+            pkt.p2pHeader.ackID = rand()%0x8FFFFFF0 + rand_helper++;
+            pkt.p2pHeader.ackUID = 0;
+            pkt.p2pHeader.ackDataSize = 0;
+            // big endian
+            pkt.p2pFooter.appID = little2big_endian(session.appID);
+
+            std::ostringstream content;
+            content.write("\x80\x00\x09\x00\x08\x00\xD8\x02\x00\x00", 10);
+            content.write((char*)filenameutf16, xml.size()*2-1);
+            content.write("\x00\x0d\x00\x0a\x00\x0d\x00\x0a\x00\x00\x00", 11);
+            pkt.body = content.str();
+
+            sendP2PPacket(conn, pkt, session);
+
+            s = conn.myNotificationServer()->externalCallbacks.listenOnPort(&conn, 6891);
+        }
+
+        else if (content == "receivedViewerData")
+        {
+            conn.myNotificationServer()->externalCallbacks.waitForConnection(&conn, s);
+        }
+
+        data = "";
+
+    }
+
+
     void P2P::sendP2PData(MSN::SwitchboardServerConnection &conn, p2pSession &session, p2pPacket &packet)
     {
         p2pPacket pkt_part = session.tempPacket;
@@ -441,6 +690,23 @@
         buf_ << "MSG " << conn.trID << " D " << full_msg.str().size() << "\r\n";
         buf_ << full_msg.str();
 
+        debug_start();
+        debug_line("Send P2P Data!");
+        debug_line("Session ID:      " << pkt_part.p2pHeader.sessionID);
+        debug_line("Identifier:      " << pkt_part.p2pHeader.identifier);
+        debug_line("Data offset:     " << pkt_part.p2pHeader.dataOffset);
+        debug_line("Total data size: " << pkt_part.p2pHeader.totalDataSize);
+        debug_line("Message Length:  " << pkt_part.p2pHeader.messageLength);
+        debug_line("Flag:            " << pkt_part.p2pHeader.flag);
+        debug_line("Ack ID:          " << pkt_part.p2pHeader.ackID);
+        debug_line("Ack UID:         " << pkt_part.p2pHeader.ackUID);
+        debug_line("Ack Data size:   " << pkt_part.p2pHeader.ackDataSize);
+        debug_line("Body:");
+        debug_line(packet.body);
+        debug_line("Footer:          " << pkt_part.p2pFooter.appID);
+        debug_end();
+
+
         if (conn.write(buf_) != buf_.str().size())
             return;
 
@@ -506,6 +772,23 @@
             buf_ << "MSG " << conn.trID++ << " D " << full_msg.str().size() << "\r\n";
             buf_ << full_msg.str();
 
+            debug_start();
+            debug_line("Send P2P Packet!");
+            debug_line("Session ID:      " << packet.p2pHeader.sessionID);
+            debug_line("Identifier:      " << packet.p2pHeader.identifier);
+            debug_line("Data offset:     " << packet.p2pHeader.dataOffset);
+            debug_line("Total data size: " << packet.p2pHeader.totalDataSize);
+            debug_line("Message Length:  " << packet.p2pHeader.messageLength);
+            debug_line("Flag:            " << packet.p2pHeader.flag);
+            debug_line("Ack ID:          " << packet.p2pHeader.ackID);
+            debug_line("Ack UID:         " << packet.p2pHeader.ackUID);
+            debug_line("Ack Data size:   " << packet.p2pHeader.ackDataSize);
+            debug_line("Body:");
+            debug_line(packet.body);
+            debug_line("Footer:          " << packet.p2pFooter.appID);
+            debug_end();
+
+
             if (conn.write(buf_) != buf_.str().size())
                 return;
         }
@@ -520,7 +803,7 @@
         Message::Headers header_app = Message::Headers(msg[1]);
         switch(session.appID)
         {
-            case APP_FILE_TRANSFER:
+            case APP_FILE_TRANSFER or APP_WEBCAM:
             {
                 session.CSeq = decimalFromString(header_slp["CSeq"]);
                 session.Bridges = header_app["Bridges"];
@@ -592,6 +875,9 @@
 
     void P2P::handle_INVITE(MSN::SwitchboardServerConnection &conn, p2pPacket &packet)
     {
+        std::cout << "handle invite" << std::endl;
+        std::flush(std::cout);
+
         p2pSession session;
         std::vector<std::string> msg = splitString(packet.body, "\r\n\r\n");
         msg[1]+="\r\n"; // this is really needed :(
@@ -610,9 +896,15 @@
         session.CallID = header_slp["Call-ID"];
         session.ContentType = header_slp["Content-Type"];
 
+        std::cout << "look for: " << session.CallID << std::endl;
+        std::flush(std::cout);
+
         std::map<unsigned int, p2pSession>::iterator i = startedSessions.begin();
         for(; i != startedSessions.end(); i++)
         {
+            std::cout << (*i).first << ": " << (*i).second.CallID << std::endl;
+            std::flush(std::cout);
+
             if((*i).second.CallID == session.CallID)
             {
                 // this isn't a new session, since I already have this callid
@@ -622,6 +914,8 @@
                 old_session.CSeq = session.CSeq;
                 old_session.ContentType = session.ContentType;
                 // handle the changes received in this invitation packet
+                std::cout << "handle session changes!!!" << std::endl;
+                std::flush(std::cout);
                 handle_session_changes(conn, packet, old_session);
                 return;
             }
@@ -641,19 +935,34 @@
         session.baseIdentifier++;
         session.step = STEP_ACK_INVITATION_SENT;
 
+        std::cout << "Session appID is: " << session.appID << std::endl;
+        std::flush(std::cout);
+
         switch(session.appID)
         {
             case APP_WEBCAM:
             {
-                if(header_app["EUF-GUID"] == "{4BD96FC0-AB17-4425-A14A-439185962DC8}")
+                std::cout << "GUID is: " << header_app["EUF-GUID"] << std::endl;
+                std::flush(std::cout);
+                if(header_app["EUF-GUID"] == "{4BD96FC0-AB17-4425-A14A-439185962DC8}") // Media session
                 {
                     //conn.myNotificationServer()->externalCallbacks.askWebCam(&conn, session.sessionID);
+                    std::cout << "want media session!" << std::endl;
+                    std::flush(std::cout);
+                    
                     std::string body("SessionID: "+ toStr(session.sessionID) +"\r\n");
                     send_200OK(conn, session, body);
+                    //handle_session_changes(conn, packet, session);
                 }
-                if(header_app["EUF-GUID"] == "{1C9AA97E-9C05-4583-A3BD-908A196F1E92}")
+                if(header_app["EUF-GUID"] == "{1C9AA97E-9C05-4583-A3BD-908A196F1E92}") // Media receive only
                 {
-                    //conn.myNotificationServer()->externalCallbacks.askWebCam(&conn, session.sessionID);
+                    startedSessions[session.sessionID]=session;
+
+                    std::cout << "want webcam receive!\n";
+                    std::flush(std::cout);
+
+                    conn.myNotificationServer()->externalCallbacks.askWebCam(&conn, session.sessionID);
+
                 }
                 break;
             }
@@ -938,6 +1247,9 @@
 
     void P2P::send_603Decline(MSN::SwitchboardServerConnection &conn, p2pSession &session)
     {
+        std::cout << "sending decline!" << std::endl;
+        std::flush(std::cout);
+
         p2pPacket packet;
 
         std::ostringstream body2;
@@ -948,6 +1260,7 @@
                 "From: <msnmsgr:"+session.from+">\r\n"
                 "Via: "+session.Via+"\r\n"
                 "CSeq: "+ toStr(++session.CSeq) + "\r\n"
+                //"CSeq: "+ toStr(1) + "\r\n"
                 "Call-ID: "+session.CallID+"\r\n"
                 "Max-Forwards: 0\r\n"
                 "Content-Type: application/x-msnmsgr-sessionreqbody\r\n"
@@ -1050,21 +1363,46 @@
         this->removeCallback(packet.p2pHeader.ackUID);
         p2pSession session = startedSessions[sessionID];
         session.step = STEP_SENDING;
-        std::string filepath;
-        filepath += b64_decode(session.Context.c_str()); // prevents empty context
-        if(filepath.length())
+
+        switch(session.appID)
         {
-            if(!conn.myNotificationServer()->msnobj.getMSNObjectRealPath(b64_decode(session.Context.c_str()), session.filename))
+            case APP_FILE_TRANSFER:
             {
-                send_603Decline(conn,session);
+                std::string filepath;
+                filepath += b64_decode(session.Context.c_str()); // prevents empty context
+                if(filepath.length())
+                {
+                    if(!conn.myNotificationServer()->msnobj.getMSNObjectRealPath(b64_decode(session.Context.c_str()), session.filename))
+                    {
+                        std::cout << "DECLINE! " <<  __FUNCTION__ << " " << __FILE__ << ":" << __LINE__ << std::endl;
+                        std::flush(std::cout);
+
+                        send_603Decline(conn,session);
+                        return;
+                    }
+                }
+                else
+                {
+                    std::cout << "DECLINE! " <<  __FUNCTION__ << " " << __FILE__ << ":" << __LINE__ << std::endl;
+                    std::flush(std::cout);
+                    send_603Decline(conn,session);
+                    return;
+                }
+                break;
+            }
+
+            /*
+            case APP_WEBCAM:
+            {
+                std::string body("SessionID: "+ toStr(session.sessionID) +"\r\n");
+                send_200OK(conn,session,body);
                 return;
             }
+            */
+
+
         }
-        else
-        {
-            send_603Decline(conn,session);
-            return;
-        }
+
         sendP2PData(conn, session, packet);
     }
 
@@ -1091,10 +1429,35 @@
         else // user rejected
         {
             // I dont want to receive your file, blergh
+            std::cout << "DECLINE! " <<  __FUNCTION__ << " " << __FILE__ << ":" << __LINE__ << std::endl;
+            std::flush(std::cout);
             send_603Decline(conn,session);
         }
     }
 
+    void P2P::handle_webcamResponse(MSN::SwitchboardServerConnection &conn, unsigned int sessionID, bool response)
+    {
+        p2pSession session = startedSessions[sessionID];
+        std::cout << "Session ID:" << sessionID << std::endl;
+        std::cout << session.sessionID << ";" << session.to << ";" << session.from << ";" << std::endl;
+        std::flush(std::cout);
+        if(response) // user accepted
+        {
+            std::cout << "send accept" << std::endl;
+            std::flush(std::cout);
+            session.in_stream = new std::ofstream;
+            std::string body("SessionID: "+ toStr(session.sessionID) +"\r\n");
+            send_200OK(conn, session, body);
+        }
+        else // user rejected
+        {
+            // I dont want to receive your webcam, blergh
+            std::cout << "DECLINE! " <<  __FUNCTION__ << " " << __FILE__ << ":" << __LINE__ << std::endl;
+            std::flush(std::cout);
+            send_603Decline(conn,session);
+        }
+    }
+
     void P2P::handle_DataACK(MSN::SwitchboardServerConnection &conn, unsigned int sessionID, p2pPacket &packet)
     {
         this->removeCallback(packet.p2pHeader.ackUID);
Index: msn/switchboardserver.h
===================================================================
--- msn/switchboardserver.h	(Revision 120)
+++ msn/switchboardserver.h	(Arbeitskopie)
@@ -148,6 +148,7 @@
         /** Response to a file transfer invitation
          */
         void fileTransferResponse(unsigned int sessionID, std::string filename, bool response);
+        void webcamResponse(unsigned int sessionID, bool response);
 
         /** Cancel a file transfer in progress
          */
@@ -208,6 +209,8 @@
         /** Request a display picture
          */
         void requestDisplayPicture(unsigned int id, std::string filename, std::string msnobject);
+
+        void receivedWebcamData(MSN::SwitchboardServerConnection *conn, char* data, int len);
 protected:
         virtual void handleIncomingData();
         SwitchboardServerState _connectionState;
Index: msn/externals.h
===================================================================
--- msn/externals.h	(Revision 120)
+++ msn/externals.h	(Arbeitskopie)
@@ -308,14 +308,22 @@
          */
         virtual void askFileTransfer(MSN::SwitchboardServerConnection *conn, MSN::fileTransferInvite ft) = 0;
 
-        virtual int listenOnPort(int port) = 0;
+        virtual int listenOnPort(MSN::SwitchboardServerConnection *conn, int port) = 0;
 
+        virtual int waitForConnection(MSN::SwitchboardServerConnection *conn, int s) = 0;
+
+        virtual void decodedWebcamFrame(MSN::SwitchboardServerConnection *conn, const char *data) = 0;
+
         virtual std::string getOurIP() = 0;
 
         virtual std::string getSecureHTTPProxy() = 0;
 
         virtual int getSocketFileDescriptor (void *sock) = 0;
 
+        /** Notifies your application that someone is trying to make a webcam session.
+          */
+        virtual void askWebCam(MSN::SwitchboardServerConnection * /*conn*/, unsigned int sessionID) = 0;
+
         /** Asks your application to get @c size bytes of data available in @p sock
          * and store them in @p data.
          * It must return the real size written to @p data
Index: msn/debug.h
===================================================================
--- msn/debug.h	(Revision 0)
+++ msn/debug.h	(Revision 0)
@@ -0,0 +1,36 @@
+#ifndef DEBUG_H
+#define DEBUG_H
+
+#define DEBUG
+
+#ifdef DEBUG
+#include <ctime>
+#include <iostream>
+
+std::string GetTime()
+{
+    time_t curtime = time(0);
+    tm now=*localtime(&curtime);
+
+    char dest[256]={0};
+    const char format[]="%X";
+    strftime(dest, sizeof(dest)-1, format, &now);
+    return dest;
+}
+
+
+#define debug_start() std::cout << "*** " << GetTime() << " - " << __FUNCTION__ << ": " << "in " << __FILE__ << ":" << __LINE__ << std::endl;
+#define debug_line(a)  std::cout << "* " << a << std::endl;
+#define debug_end() std::cout << "***" << std::endl << std::endl; std::flush(std::cout);
+
+
+#else
+
+void debug_start() { }
+void debug_line() { }
+void debug_end() { }
+
+#endif /* DEBUG */
+
+
+#endif // DEBUG_H
Index: msn/CMakeLists.txt
===================================================================
--- msn/CMakeLists.txt	(Revision 120)
+++ msn/CMakeLists.txt	(Arbeitskopie)
@@ -61,7 +61,19 @@
     libsiren/rmlt.h
     libsiren/siren7.h
 )
+
+INCLUDE( ${CMAKE_ROOT}/Modules/FindPkgConfig.cmake )
+pkg_search_module(MIMIC REQUIRED libmimic)
+
+INCLUDE_DIRECTORIES(${MIMIC_INCLUDE_DIRS})
 add_library(msn SHARED ${msn_STAT_SRCS} ${siren_STAT_SRCS})
+
+TARGET_LINK_LIBRARIES(msn ${MIMIC_LIBRARIES})
+#set_target_properties(msn PROPERTIES COMPILE_FLAGS "${MIMIC_CFLAGS}"
+#                                     LINK_FLAGS ${MIMIC_LDFLAGS}
+#)
+
+
 set_target_properties(msn PROPERTIES VERSION 0.3.0 
                                      SOVERSION 0.3
 )
Index: msn/switchboardserver.cpp
===================================================================
--- msn/switchboardserver.cpp	(Revision 120)
+++ msn/switchboardserver.cpp	(Arbeitskopie)
@@ -35,6 +35,11 @@
 #include <cassert>
 #include <sstream>
 
+#include <fstream>
+
+
+#include <mimic.h>
+
 namespace MSN
 {
     std::map<std::string, void (SwitchboardServerConnection::*)(std::vector<std::string> &)> SwitchboardServerConnection::commandHandlers;
@@ -688,6 +693,13 @@
         p2p.handle_fileTransferResponse(*this,sessionID, filename, response);
     }
 
+    void SwitchboardServerConnection::webcamResponse(unsigned int sessionID, bool response)
+    {
+        std::cout << "in SwitchboardServerConnection::webcamResponse\n";
+        std::flush(std::cout);
+        p2p.handle_webcamResponse(*this,sessionID, response);
+    }
+
     void SwitchboardServerConnection::callback_AnsweredCall(std::vector<std::string> & args, int trid, void * data)
     {
         this->assertConnectionStateIs(SB_WAITING_FOR_USERS);        
@@ -746,4 +758,115 @@
         this->assertConnectionStateIsAtLeast(SB_CONNECTED);
         p2p.cancelTransfer(*this,id);
     }
+
+    void SwitchboardServerConnection::receivedWebcamData(MSN::SwitchboardServerConnection *conn, char* data, int len)
+    {
+        static bool initialized = false;
+        static bool finished = true;
+        static int currentLength = 0;
+        static int length = 0;
+        static char encoded[20000];
+        static MimCtx* mimic = mimic_open ();
+
+
+        unsigned char header_size;
+
+        unsigned short int width;
+        unsigned short int height;
+
+        unsigned int tag;
+
+        if (finished)
+        {
+            header_size = data[0];
+            if (header_size != 24)
+            {
+                std::cout << "invalid header size" << std::endl;
+                std::flush(std::cout);
+                return;
+            }
+
+            width = (unsigned char)data[2] | ((unsigned char)data[3] << 8);
+            height = (unsigned char)data[4] | ((unsigned char)data[5] << 8);
+
+            //std::cout << "width is " << width << "(" << (int)data[2] << ":" << (int)data[3] << ")" << std::endl;
+            //std::cout << "height is " << height << "(" << (int)data[4] << ":" << (int)data[5] << ")" << std::endl;
+
+            if(!(width==320 && height==240) && !(width==160 && height==120))
+            {
+                std::cout << "invalid width or height!" << std::endl;
+                std::flush(std::cout);
+                return;
+            }
+
+
+            length = (unsigned char)data[8] | ((unsigned char)data[9] << 8) | ((unsigned char)data[10] << 16) | ((unsigned char)data[11] << 24);
+
+            tag = data[12] | (data[13] << 8) | (data[14] << 16) | (data[15] << 24);
+
+            if(tag != ('M' | ('L' << 8) | ('2' << 16) | ('0' << 24)))
+            {
+                std::cout << "invalid tag!" << std::endl;
+                std::flush(std::cout);
+                return;
+            }
+
+            finished = false;
+
+            //std::cout << "header okay!" << std::endl;
+            //std::flush(std::cout);
+
+            memset(encoded, 0, 20000);
+        }
+        else
+        {
+            memcpy(encoded + currentLength, data, len);
+
+            currentLength += len;
+
+            //std::cout << "currentLength: " << currentLength << std::endl << "total length: " << length << std::endl;
+            //std::flush(std::cout);
+
+            if (currentLength >= length)
+            {
+                //std::cout << "frame is complete!" << std::endl;
+                //std::flush(std::cout);
+
+
+                if (!initialized)
+                {
+                    if (!mimic_decoder_init(mimic, (guchar*)encoded))
+                    {
+                        std::cout << "mimic could not be initialized!" << std::endl;
+                        std::flush(std::cout);
+                        return;
+                    }
+                    initialized = true;
+                }
+
+                int buffer_size;
+                mimic_get_property(mimic, "buffer_size", &buffer_size);
+                guchar decoded[buffer_size];
+
+                if (!mimic_decode_frame(mimic, (guchar*)encoded, decoded))
+                {
+                    std::cout << "mimic could not decode frame!" << std::endl;
+                    std::flush(std::cout);
+
+                    finished = true;
+                    currentLength = 0;
+                    return;
+                }
+
+                finished = true;
+                currentLength = 0;
+
+                //std::cout << "decoded webcam frame!" << std::endl;
+                //std::flush(std::cout);
+
+                conn->myNotificationServer()->externalCallbacks.decodedWebcamFrame(conn, (const char*)decoded);
+            }
+        }
+    }
+
 }
Index: msn/p2p.h
===================================================================
--- msn/p2p.h	(Revision 120)
+++ msn/p2p.h	(Arbeitskopie)
@@ -91,6 +91,7 @@
                 FLAG_ERROR = 0x8,
                 FLAG_DATA_EMOTICONS = 0x20,
                 FLAG_DATA_PICTURE = 0x20,
+                FLAG_WEBCAM = 0x1000000,
                 FLAG_FILE_DATA = 0x01000030,
                 FLAG_FILE_DATA2 = 0x01000020
             };
@@ -213,6 +214,9 @@
             void receiveP2PData(MSN::SwitchboardServerConnection &conn, 
                     p2pPacket &packet);
 
+            void receiveP2PWebcamData(MSN::SwitchboardServerConnection &conn,
+                    p2pPacket &packet);
+
             void handle_negotiation(MSN::SwitchboardServerConnection &conn, 
                     p2pPacket &packet);
 
@@ -275,6 +279,10 @@
                     std::string filename,
                     bool response);
 
+            void handle_webcamResponse(MSN::SwitchboardServerConnection &conn,
+                    unsigned int sessionID,
+                    bool response);
+
             void handle_session_changes(MSN::SwitchboardServerConnection &conn, 
                     p2pPacket &packet, 
                     p2pSession &session);
Index: msntest/msntest.cpp
===================================================================
--- msntest/msntest.cpp	(Revision 120)
+++ msntest/msntest.cpp	(Arbeitskopie)
@@ -37,6 +37,7 @@
 #include <openssl/ssl.h>
 
 #include <msn/msn.h>
+
 #include <string>
 #include <iostream>
 
@@ -156,8 +157,14 @@
 
     virtual void askFileTransfer(MSN::SwitchboardServerConnection *conn, MSN::fileTransferInvite ft);
 
-    virtual int listenOnPort(int port);
+    virtual void askWebCam(MSN::SwitchboardServerConnection *conn, unsigned int sessionID);
+
+    virtual int listenOnPort(MSN::SwitchboardServerConnection *conn, int port);
+
+    virtual int waitForConnection(MSN::SwitchboardServerConnection *conn, int s);
     
+    virtual void decodedWebcamFrame(MSN::SwitchboardServerConnection *conn, const char *data);
+
     virtual std::string getOurIP();
 
     virtual int getSocketFileDescriptor (void *sock);
@@ -477,6 +484,7 @@
         clientid += MSN::InkGifSupport;
         clientid += MSN::SIPInvitations;
         clientid += MSN::SupportMultiPacketMessaging;
+        clientid += MSN::SupportWebcam;
 
         mainConnection.setState(MSN::buddyStatusFromString(state), clientid);
     } else if (!strcmp(command, "friendlyname")) {
@@ -1106,7 +1114,7 @@
     return (void*)s;
 }
 
-int Callbacks::listenOnPort(int port)
+int Callbacks::listenOnPort(MSN::SwitchboardServerConnection* conn, int port)
 {
     int s;
     struct sockaddr_in addr;
@@ -1127,8 +1135,73 @@
     }
     
     return s;
+
 }
 
+int Callbacks::waitForConnection(MSN::SwitchboardServerConnection* conn, int s)
+{
+
+    struct sockaddr_in addr;
+    char data[15001];
+
+    int n;
+    std::string tmp;
+
+    std::cout << "wait for accept" << std::endl;
+    std::flush(std::cout);
+
+    socklen_t len = sizeof(addr);
+    int x = accept(s, (struct sockaddr *)(&addr), &len);
+
+    std::cout << "accepted" << std::endl;
+    std::flush(std::cout);
+
+    while(1)
+    {
+        bzero(data,15001);
+        n = read(x,data,15000);
+
+        //std::cout << "read " << n << " - " << data << std::endl;
+
+        tmp = data;
+        if (tmp.substr(0, 11) == "recipientid")
+        {
+            std::cout << "is recipientid!" << std::endl;
+            std::flush(std::cout);
+            write(x,"connected\x0d\x0a\x0d\x0a",13);
+            continue;
+        }
+        else if (tmp.substr(0, 9) == "connected")
+        {
+            std::cout << "is connected!" << std::endl;
+            std::flush(std::cout);
+            continue;
+        }
+
+        conn->receivedWebcamData(conn, (char *)data, (int)n);
+
+    }
+
+    return n;
+
+}
+
+void Callbacks::decodedWebcamFrame(MSN::SwitchboardServerConnection *conn, const char *data)
+{
+    static int i = 0;
+    i++;
+
+    int status;
+    status = mkdir("/tmp/webcamdata", S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+
+    std::stringstream filename;
+    filename << "/tmp/webcamdata/frame";
+    filename << i;
+
+    std::ofstream webcamFile(filename.str().c_str(), std::ios::out);
+    webcamFile << data;
+}
+
 std::string Callbacks::getOurIP(void)
 {
     struct hostent * hn;
@@ -1180,6 +1253,12 @@
     conn->fileTransferResponse(ft.sessionId, filename2, true);
 }
 
+void Callbacks::askWebCam(MSN::SwitchboardServerConnection *conn, unsigned int sessionID)
+{
+    std::cout << "Somebody wants to make a webcam session..." << std::endl;
+    conn->webcamResponse(sessionID, true);
+}
+
 void Callbacks::addedContactToGroup(MSN::NotificationServerConnection * conn, bool added, std::string groupId, std::string contactId)
 {
     if(added)
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys

class WebcamData(QDialog):
   def __init__(self, parent=None):
      QDialog.__init__(self, parent)

      self.mainlayout = QVBoxLayout(self)
      self.diredit = QLineEdit(self)

      # Complete directory names in folder QLineEdit
      self.completer = QCompleter()
      self.completionmodel = QDirModel(self.completer)
      self.completionmodel.setFilter(QDir.AllDirs)
      self.completer.setModel(self.completionmodel)
      self.diredit.setCompleter(self.completer)

      self.splitter = QSplitter(Qt.Horizontal, self)
      self.piclabel = QLabel(self)
      self.piclabel.setMinimumSize(320, 240)
      self.piclabel.setMaximumWidth(320)

      self.filemodel = QFileSystemModel(self)
      self.filemodel.setFilter(QDir.Dirs | QDir.Files | QDir.NoDotAndDotDot)
      #self.filemodel.setFilter(QDir.Files)
      self.filemodel.setRootPath("/")
      self.fileview = QListView(self)
      self.fileview.setModel(self.filemodel)

      self.splitter.addWidget(self.piclabel)
      self.splitter.addWidget(self.fileview)
      self.mainlayout.addWidget(self.diredit)
      self.mainlayout.addWidget(self.splitter)

      self.connect(self.diredit, SIGNAL("returnPressed()"), self.updateFiles)
      self.connect(self.fileview, SIGNAL("doubleClicked ( const QModelIndex & )"), self.setPath)
      self.connect(self.fileview, SIGNAL("clicked ( const QModelIndex & )"), self.setImage)

      self.diredit.setText("/tmp/webcamdata/")      

      self.setWindowTitle("libmsn webcam viewer - Lukas Hetzenecker")
      self.updateFiles() 
      self.resize(750, 550)
      self.show()

   def updateFiles(self):
      dir = self.diredit.text()
      self.fileview.setRootIndex(self.filemodel.index(dir))
      self.piclabel.setText("No file selected!")

   def setPath(self, index):
      if self.filemodel.fileInfo(index).isDir():
         self.diredit.setText(self.filemodel.filePath(index))
         self.updateFiles()

   def setImage(self, index):
      if self.filemodel.fileInfo(index).isFile():
         cont = file(unicode(self.filemodel.filePath(index))).read()
         image = QImage(cont, 320, 240, QImage.Format_RGB888)
         pixmap = QPixmap.fromImage(image)
         self.piclabel.setPixmap(pixmap)

app = QApplication(sys.argv)
w = WebcamData()
app.exec_()
_______________________________________________
kopete-devel mailing list
kopete-devel@kde.org
https://mail.kde.org/mailman/listinfo/kopete-devel

Reply via email to