Wednesday, June 28, 2006

Problems Resolved: Bugs and my understanding of NMM.

Part I kde bug.
During the last 2 weeks I had to fight against a KDE4 (kdelibs) bug: I was no more able to build a proper KDE System Configuration Cache ksycoca. Everything was set up according to the docs, the debug output of kde apps was not spotting any problem, some components were even able to run... but I could not use kcmshell (the KDE control center application and framework).
The problem with ksycoca was triggered by the presence of the /etc/xdg/menus/applications.menu file. That file is installed by the gnome-menus package on kubuntu dapper (the distro I'm using). The effect of the the bug is that kbuildsycoca is no more able to find the proper KDE4 .menu files after processing /etc/xdg/menus/applications.menu.
Finding that out was really difficolt (I'd never been able to do that on my own, Vir and dfaure helped a lot on freenode #phonon and #kde4-devel): first we had to find out that the problem was ksycoca related, then we had to figure out that this problem was caused by the presence of /etc/xdg/menus/applications.menu even if kbuildsycoca does not print a single warning or error when analyzing that file.

Part II: ByteStreamNode properly coded.
My prevoius attempt to write the ByteStreamNode was succesfull in practice, but it was coded without compliance to the NMM architecture (I was not skilled enought).
Thanks to the the help of Marco (my menthor) I finally understood how to pass data to remote running nodes using NMM proxies and interface. NMM is a so powerfull architecture that understanding all of it at a time is hard even if the docs are very usefull and cover most aspect.
Using sample code that Marco sent me I could code a proper NMM::Interface for ByteStreamNode.
The interface is:
module NMM{
interface IByteStreamNode {
void sendBuffer2(in TransferBuffer2 buffer);
int getSize();
int getMaxSize();
};
}

Using ByteStreamNode and its interface IByteStreamNode it is possible to inject arbitrary data from applications into NMM flow graphs. To achieve this the application developer creates the flow graph as described in the NMM documentiation using NMM::ByteStreamNode as the source node and using its interface to send data to the node. Data sent to the node is buffered into a streamqueue from where processBuffer (the "main/core" function of NMM nodes) extracts it later. The application can query the current fill size and maximum allowed size of the internal streamqueue using the interface functions getSize and getMaxSize.

The only cave at for using ByteStreamNode is that the first chunk of data must be passed to the node (using sendBuffer2) before initOutput is called in order to let initOutput to find data mime-type (using libmagic).

The phonon nmm backend uses ByteStreamNode as a fallback when a NMM unsupported protocol is specified in the url to be played. For such urls NMM throws an exception in GraphHandler::stage1 which is catched by Phonon and used to trigger the creation of a Phonon::ByteStream object. That object is responsible for calling GraphHandler::stage1_bs and passing data to the returned IByteStreamNode interface. Another task for Phonon::ByteStream is to suspend and resume the KIO::TransferJob as soon as the ByteStreamNode's internal StreamQueue gets full or empty.

Part III: plans
After all this learning and coding I'm becoming more experienced with both NMM andn Phonon.
I thought I would have been able to proceed faster, but KDE4 beeing not mature and NMM beeing a completely new concept to me really slowed me down since now.

My next steps will be:
  1. Resurrect the config-app
  2. Implement AudioOutputDevice selection (the interface that let users chose which NMM audio output node to use)
  3. VolumeControlNode: a software volume slider node
One idea I would like to implement is a app/user interface that detects other NMM hosts on the network and shows theyr speakers to the user. Using this is interface it would be possible to use a desktop computer and a laptop computer as a 4 speaker system (-almost- anyone has a laptop and a desktop nowdays).

Friday, June 09, 2006

The birth of the ByteStreamNode
and its problems

First of all let me explain how Phonon-nmm interaction works.
Suppose a KDE4-NMM user whats to play an url, when behind the scenes the following happens:

  • Phonon looks for a Phonon backend (Phonon-NMM in this case) and loads it

  • The backend configures its media framework (the backend is the Phonon::nmm::Backend object, the framework is NMM)

  • Phonon creates a Phonon:nmm::MediaObject and calls its setUrl()method

  • If the setUrl call fails the MediaObject is destroied and a Phonon:nmm::ByteStream object is created which receives the raw stream data in a writeData() callback method (actually a qt-slot) from a KDE4 KIO::TransferJob instance.

  • After an Phonon::nmm::AbstractMediaProducer subclass, MediaObject or the ByteStream, is setup Phonon assumes the media framework is ready to start

  • The AbstractMediaProducer::play method is called which call the media framework playing functionality


The glue between NMM and Phonon is the NMM::GraphHandler class. This class is meant for creating flowgraph from an URL and for playing it. If NMM cannot understand the url (maybe because the protocol is not supported by NMM) GraphHandler throws an exception that Phonon detects (that functionality is in GraphHandler::stage1() which is called from MediaObject::setUrl() ). If stage1() throws any exception, Phonon (in the ByteStream class) calls GraphHandler::stage1_bs() from the ByteStream::writeData().
The writeData() method is responsible for coping the data packets incoming from the running KIO::TransferJob into a local buffer (actually an NMM::StreamBuffer instance) and for initaliazing the GraphHandler by means of calling its stage1_bs() when the first chunk of stream is available. Basically untill the the GraphHandler is initialized writeData pushes incoming data into the buffer and tries to initalize GraphHandler using that growing buffer.
The GraphHandler initialization in stage1_bs() (whose prototype is void stage1_bs(NMMApplication& app, StreamQueue* sq)) creates a new ByteStreamNode from the rgistry and registers the sq into the ByteStreamNode.
For that purpose ByteStreamNode implements the IByteStreamNode interface which just defines void setStreamQueue(in int sq). As you can see this is really an awfull trick I did.
That interface should take a StreamQueue* as input, but I was not able to define it that way (I need input from Marco here). The other alternative would be to create the the ByteStreamNode from stage1_bs() without using the registry (and without defining any IByteStreamNode) and then getting its INode interface using getInterface(); this way the ByteStreamNode could be compiled with GraphHandler, and not as a plugin. Maybe this is better?

Once the ByteStreamNode is started all it has to do in processBuffer is to get one buffer from the streamQueue filled by KIO and return it.
The only other functionalty of ByteStreamNode is stream type finding, done using the first buffer in the registered streamBuffer and passing its data to libmagic.

Monday, June 05, 2006

SoC report

This is the first report about my SoC phonon-nmm project.
So far I have studied the Phonon and NMM code, tried to resolve an rtti bug in NMM event handling (my co mentor mkrez fixed it) and I added some functionality into the phonon nmm backend and in the GraphHandler class to support the phonon bytestream KIO data passing interface.

At the moment I evolved NMM::GraphHandler and Phonon::nmm::ByteStream making them capable of using a ByteStreamNode (yet to be implemented) to pass data.

What I did was to change the GraphHandler::stage1 method to make it able to get an StreamQueue as optional input. What stage1 now does is to try to detect the media steram format from the NMM::Buffers in the NMM::StreamQueue.In order to do that I added a tiny helper function that uses libmagic to detect mime type.

On the Phonon side I implemented some functionality into ByteStream.
The ByteStream object tries to initalize the GraphHandler object as soon as the first data chunck arrives (in the ByteStream::writeData member). The data to be passed to NMM is inserted into a StreamQueue object that is registered into the ByteStyreamNode during GraphHandler::stage1.