At first a hint: LibGPF is still in pre-alpha state, while this document
is shortly before LibGPF's beta state. So, please don't wonder, when the
code doesn't do what is mentioned here in many cases. Please keep that in
mind. If you feel you want to change that state, you'll welcome to help
out in the development of LibGPF. Thanks.


Introduction
============

The initial motivation for LibGPF was to load still-image files and convert
between formats, without incurring the huge overhead that trivial systems
such as the pnmtools package do, because of their converting the entire
image to a common intermediate format. LibGPF includes provisions for
shallow-buffered conversion in a tile or row based method. LibGPF's
conversion sublibs (pipe) would find the shallowest buffer length needed to
perform the conversion between an input sublib and an output sublib, while
still maintaining modularity of image readers and loaders. To our knowlege,
though more advanced graphics programs such as the GIMP use tile-based
algorithms, LibGPF is the only API that has been conceived to process format
conversion in this way and retain all the benefits of a plugin system.

It was decided early in the design stage that LibGPF should also encompass
motion image files, since many of the same concepts and API apply, rather
than engender redundancy, though a full workup of the video related
functionality in LibGPF was not at that time attempted. A recent review of
<link to "http://www.gstreamer.net/">GStreamer</link> has led to some
rethinking of the LibGPF API to include equivalent features, and to make
use of GStreamer plugins as a fallback source of io and conversion
routines.  This refactoring is not entirely complete as of yet.

LibGPF differs from GStreamer in the following intrinsic points:

1) Decisions about threading are left to the user, who is provided with
the necessary information about the real-time and flow control
characteristics and entry points of a pipeline once it is built.  Threads
are never automatically created unless they are absolutely necessary, and
libpthreads is not required in the base library. However, there is room
in LibGPF for common-sense convenience functions to aid spawning threads.

2) Rate control must not be solely provided by passing DTR/CTS signals
through the pipeline. Sources, sinks, and convertors should accept
real-time parameters requesting throttling at specific traffic bitrates,
though they will naturally be ignored by objects that have no intrinsic
real-time properties. This allows a pipeline to be driven by hardware *at
a desired rate* when any of the components of the pipeline are driving
hardware which has real-time properties.

3) A focus is placed on keeping buffers shallow and timeslices small,
unless tuned by the application to be more aggressive or jitter-tolerant.
Entry points and objects in the pipeline should provide basic real-time
informational elements.

4) Everything is in vanilla C, such that it can be easily wrapped by other
programming languages and has minimal dependencies.

5) LibGPF will include the ability to reverse direction of the stream in
addition to setting rates, when the stream supports this. Similarly LibGPF
implements seeking back and forth to positions in the stream, when it is
possible to do so (stored content).

Note we are not in "competition" with GStreamer, and in fact we expect
that when LibGPF has more meat to it, the situation will reverse itself
and LibGPF will be utilized by the GStreamer team as a back-end, as a
place to offload plugins while the GStreamer API/SDK becomes more
embellished. We expect a synergy to develop between the two libraries.
Where GStreamer has advantages over LibGPF, and they are advantages we do
not consider to be too high level for LibGPF, we will seek to improve
LibGPF's API. Specifically here are some desirable characteristics in
GStreamer:

1) GStreamer pipelines can be arbitrarily complex, which may give some
advantages over LibGPF's pipeline structure, though LibGPF does support a
certain level of nesting. It must be determined where the point of
diminishing returns is reached in modularizing conversion routines, and we
should adapt LibGPF's pipeline structure accordingly.

2) GStreamer abstracts queues (buffers) as a pipeline element, a concept
which may have a lot of potential for LibGPF.

3) GStreamer is mature enough to have developed a vocabulary describing
the pipeline ("pad", "sink", etc). To aid potential cross-development and
understanding, we should import some of this nomenclature.



The big picture
===============

LibGPF is very modular. It consists of the base lib, target- , protocol-
and the pipe sublibs.

The base lib handles the dynamic loading of all sublibs and computes the
fastest/optimal way for the data transfer.

The targets are used to be either the source (input) or the destination
(output) of the data-stream, where the data is transfered between. They
have all information about the data; they manage, but they don't know,
where the data actually comes from or goes to physically. The targets can
also perform some format conversions of the data as a kind of
pre/post-conversion in order to optimize the pipeline more or less
agressively.

The protocol sublibs know where the data physically comes from or goes to.
They are used by the targets to transparently read/save data from/to the
memory/disk/network/internet.

The pipe sublibs are intended to convert the data coming from the input
target into a suitable form for the output target.

Now, you're surely wondering how these parts interact with each other. Have
a look at this figure to get a rough picture how things work together:


protocol1 ---> target1 ---> pipe ---> target2 ---> protocol2


Here, target1 and target2 are target sublibs, where target1 is used as the
input and target2 as the output. protocol1, protocol2, target1, target2 and
pipe can be any sort of actually available sublibs in the correspondent
directories. Each protocol sublib can be used with another one. protocol1
and protocol2 can even be the same sublib. Same for target1 and target2.

So LibGPF's pipeline can be imagined as a powerful blitting operation:
from disk to disk, from LAN to disk, from disk to screen, from screen to
screen, from screen to disk, from LAN to LAN, etc... The only _real_ limit
is your hardware (i.e. if you have a diskless machine :). Other limits
come from the sublibs.

The user opens two targets, tells them which protocols they have to use, and
specifies which target interacts as the input and the output. A target can
be everything that has detailed information about a certain form of data as
like as a picture or a video. All this can be with <link to "gpfOpen() in
the API section">gpfOpen()</link>.

After setting up some <link to "Stream control section">parameters</link>
you can start the the data-transfer with <link to "gpfStartStream() in the
API section">gpfStartStream()</link>.

Here is an example:

gpf_handle_t handle;

...

/* open handle */
handle = gpfOpen(...);

/* start transfer */
gpfStartStream(handle);

/* do transfer */
while (!gpfIsStopped(handle)) {
gpfTransferNextStreamBlock(handle, NULL);
}/* while */

/* close handle */
gpfClose(handle);

See the <link to "User guide section">User guide</link> for more
information.

Now, you are surely wondering how the pipe is specified/choosed for
conversion. The most suitable pipe sublib is autodetected by the pipeline
management. It asks the targets for the exact format of the data and
searches for one or more suitable pipe sublibs. But why more than one pipe
sublibs? Isn't one enough? Well, that makes even conversions over several
pipe sublibs possible in case one suitable pipe sublib isn't available.
But how is it determined/figured out, _if_ a target should or shouldn't
perform pre/post-conversion? How reliable is the most optimal pipeline
found? A target may offer more than one possible formats (i.e. different
colordepth of a picture)!

That's a good point. Both the targets and the pipes know how fast or slow
their algorithm is (i.e. O(1), O(n), O(log n), O(n^2), etc.) and they know,
how much memory they need. The pipeline management gets all the detailed
information of all of them. So it accumulates them up and chooses the
fastest pipeline by default. The user can choose the pipeline by setting
some flags. So, the pipeline management can choose a pipeline, that uses
the less memory, for example. The user can also give a ratio of
speed<->memory usage. Then the pipeline management chooses the pipeline
with the best ratio that fits.

For more information about this algorithm go to the <link to
"Developers guide section">Developers section</link>.






User documentation
==================



Installation
------------

Getting LibGPF
--------------

All the information about latest releases can be found on the <link to
"http://www.ggi-project.org/libgpf.php">GGI Project website</link>.

The normal user is advised to get the stable release.

Third party developers are encouraged to try the development releases (or
even the CVS sources) and notify the GGI team of any bugs, or suggest
improvements, etc.


Required packages
-----------------

Core libraries and extensions
-----------------------------

The base lib requires <link to "http://www.ggi-project.org/download.php">
LibGGI</link>. What you need else depends on what you intend to do with
LibGPF.

All other packages are optional, but some might be needed by a particular
program. Keep in mind that the highlevel family extensions internally rely
on lowlevel ones, which provide common functionality found in modern
hardware. You'll need them, when you wanna use the some targets like <link
to "http://www.ggi-project.org/docs/lowlevel-book.html">LibOVL</link>,
<link to
"http://www.ggi-project.org/docs/lowlevel-book.html">LibBUF</link> and/or
<link to
"http://www.ggi-project.org/docs/lowlevel-book.html">LibBLT</link>. If you
want to use <link to
"http://www.ggi-project.org/docs/highlevel-book.html">LibVIDEO </link>,
then the three mentioned lowlevel libs are required, because it makes use
of those targets internally. But then again it's up to you.

Targets
-------

Whereas the source packages include the code for all available targets,
binary packages provide only the core libraries. Thus, you only need to
download and install the target you actually need. E.g. a MS-Windows? or a
FreeBSD? User won't need DirectFB libraries and doesn't care much about
it.

Anyway, unless you're very short on disk space, having more libraries than
needed won't hurt, so don't hesitate to get additional targets.



Building from sources
---------------------

UNIX systems
------------

Reading the README and INSTALL files for each package is more than
recommended. But for those in a hurry, the following ancestral incantation
should work fine for most people:

$ ./configure; make; make install

In case you don't have root access on your machine, you can specify a
prefix instead of the default /usr/local/.

$ ./configure --prefix=<absolute path, you have WRITE-access to>
$ make
$ make install

Don't forget to had the <chosen prefix>/lib directory to your
LD_LIBRARY_PATH environment variable.


Win32
-----

To compile LibGPF on WIN32, you'll need to install the cygwin environment
(http://www.cygwin.com). Download setup.exe and install at least the
following packages : cygwin, automake, autoconf, perl. bash might also
help. In addition, you'll need the DirectX SDK 6.1 or newer
(http://download.microsoft.com/download/win2000pro/dx8asdk/8.0a/W9X2KMe/EN-US/DX8a_SDK.exe).

You can then run (in a library directory):

$ ./configure
$ make
$ make install

If you got the source from CVS, you'll have to generate a configure.sh
before compiling a library. You'll need the autoconf and automake tools
for this.




User guide
==========



A simple data-transfer aka "My first hello world program"
---------------------------------------------------------

Before we go into the details, we wanna make a simple data transfer that
equals to loading an image file (ppm format) to the screen. If you have
questions about the API functions or its syntax used here, then please
have a look into <link to "The API section">The API</link> section, where
you find a detailed description about all of them. Further you'll find
detailed information about the <link to "LibGGI's API section">LibGGI's
API</link> functions used here, in case you have questions about them,
too.

At first we include two headers

#include <ggi/ggi.h>
#include <ggi/gpf.h>

and initialize LibGGI and LibGPF

ggi_visual_t vis;
gpf_handle_t handle;
    struct gpf_properties_t props;

/* Initialize LibGGI */
if (ggiInit() != 0) {
fprintf(stderr, "Unable to initialize LibGGI\n");
exit(1);
}/* if */

/* Initialize libGPF */
if (gpfInit() != 0) {
ggiPanic("Unable to initialize LibGPF\n");
}/* if */


open a default GGI visual with a suitable video mode

/* Open the default visual */
if ((vis = ggiOpen(NULL)) == NULL) {
ggiPanic("Unable to open default visual\n");
}/* if */

/* Set the default mode */
if (ggiSetSimpleMode(vis, GGI_AUTO, GGI_AUTO, GGI_AUTO, GT_AUTO) < 0) {
ggiPanic("Unable to set default mode\n");
}/* if */


Now, we create a gpf handle:

handle = gpfOpen("ppm",
"file:/home/myself/picture-collection/foo.ppm",
"libggi", "-vis=%p", vis);

This tells gpfOpen to use the ppm target as the input and the libggi
target as the output. We tell the ppm target to use the file protocol and
we tell the file protocol the location of the file on the disk. Further,
we tell the libggi target to use the visual we just opened.

Here is a one-liner to do a error handling. I know, there are other/better
ways than exiting the hard way :)

if (!handle) gpfPanic("Couldn't create handle\n");

Then we tell LibGPF to start the transfer from the first image. This isn't
required to do as this is the default behaviour, but doing so is always
safe. This function has something to do with the <link to "Stream control
section">stream control</link> of the data transfer.

    gpfClearProperties(&props);			/* initialize properties */
gpfSetFlags(&props, GPF_FRAME_FIRST);
    gpfSetProperties(handle, &props);

Now here we come! You surely guess right what this line does... ;-)

gpfStartStream(handle);

And here we perform the actual transfer until it's finished. We choosed to
do blockwise data-transfer in order to have a better <link to "Stream
control section">stream control</link>.

while (!gpfIsStopped(handle)) {
gpfTransferNextStreamBlock(handle, NULL);
}/* while */

Finally, we flush the output to the screen and voila - you see your
favourite picture on the screen.

ggiFlush(vis);

Errr... Yes, serious programmers don't forget to "shut down" their
programs cleanly ;-)

/* Close handle */
gpfClose(handle);

/* Close visual */
ggiClose(vis);

/* Deinitialize libGPF */
gpfExit();

/* Deinitialize LibGGI */
ggiExit();


The enthustiastic reader might want to do an exercise: Draw anything on a
GGI visual and save it anywhere as a ppm image using LibGPF. You have 15
minutes! ;-)


Another simple data-transfer aka "My second hello world program"
----------------------------------------------------------------

This time, there's nothing to see on the screen. But you'll notice, that
you'll have some less bytes of free disk space on your disk! ;-)) You
wonder why? Well, when you load a ppm file (again) and save it in
the jpeg format, you'll surely have some less bytes free on your disk,
right? :) Ok, enough spoken - let's go on.

First we include one header,

#include <ggi/gpf.h>

initialize LibGPF,

gpf_handle_t handle;
    struct gpf_properties_t props;

/* Initialize libGPF */
if (gpfInit() != 0) {
ggiPanic("Unable to initialize LibGPF\n");
}/* if */


and create a gpf handle:

handle = gpfOpen("ppm",
"file:/home/myself/picture-collection/foo.ppm",
"jpeg",
"file:/home/myself/picture-collection/foo.jpg");

This tells gpfOpen to use the ppm target as the input and the jpeg target
as the output. We tell the ppm target to use the file protocol and we tell
the file protocol the location of the file on the disk. Similar the same
for the jpeg target.

Then same as above:

if (!handle) gpfPanic("Couldn't create handle\n");

    gpfClearProperties(&props);			/* initialize properties */
gpfSetFlags(&props, GPF_FRAME_FIRST);
    gpfSetProperties(handle, &props);

gpfStartStream(handle);

while (!gpfIsStopped(handle)) {
gpfTransferNextStreamBlock(handle, NULL);
}/* while */

/* Close handle */
gpfClose(handle);

/* Deinitialize libGPF */
gpfExit();

The enthustiastic reader (again :) might want to do an exercise (again):
Load a ppm file, display it on the screen and then save it in the jpeg
format. Hint: You need to open an another handle for this. You have 15
minutes again! ;-)


Create a video
--------------

Ok, let's get more serious now! We create a mpeg1 video from a
couple of ppm files. You'll be astonished how easy that is: Use the
code lines from the previous example and replace

handle = gpfOpen("ppm",
"file:/home/myself/picture-collection/foo.ppm",
"jpeg",
"file:/home/myself/picture-collection/foo.jpg");

with

handle = gpfOpen("ppm",
"file:/home/myself/picture-collection/foo-%i.ppm
-multiple_images -use_internal_counter",
"mpeg",
"file:/home/myself/picture-collection/foo.mpeg
-format=mpeg1");

That's it! Yes, you read correctly! That's all! Compile it! Run it!


Combine a picture and a audio stream to a video stream
------------------------------------------------------

The previous example was very amazing and amusing at once, wasn't it? But
nontheless, we believe, you missed something: Sound! So we want to
compound a picture and a audio stream to a video stream. Assume we have a
FLI video - a video format, that AFAIK supports no sound, and the sound is
stored in a wav audio-file. We fit them together into an mpeg2 file. We
make use of a multi pseudo-target as the input ("pseudo" meaning, that it
uses another targets in order to get tricky things working). Here is the
code:

#include <ggi/gpf.h>

int main(void)
{
gpf_handle_t handle;
    struct gpf_properties props;

/* Initialize libGPF */
if (gpfInit() != 0) {
ggiPanic("Unable to initialize LibGPF\n");
}/* if */


handle = gpfOpen("multi",
"-targets=fli(file:/home/myself/picture-collection/foo.fli):
wav(file:/home/myself/sound-collection/foo.wav)",
"mpeg",
"file:/home/myself/picture-collection/foo.mpeg
-format=mpeg2");

if (!handle) gpfPanic("Couldn't create handle\n");

    gpfClearProperties(&props);			/* initialize properties */
gpfSetFlags(&props, GPF_FRAME_FIRST);
    gpfSetProperties(handle, &props);

gpfStartStream(handle);

while (!gpfIsStopped(handle)) {
gpfTransferNextStreamBlock(handle, NULL);
}/* while */

/* Close handle */
gpfClose(handle);

/* Deinitialize libGPF */
gpfExit();

return 0;
}/* main */




Playing a video
---------------

You surely expect a lot from LibGPF now, right? To play a video with
LibGPF you need to do, what <link to "libvideo's doc index>LibVIDEO</link>
does internally. Yes, LibVIDEO uses LibGPF (and some more libs)
internally. So I refer to <link to "libvideo's developers
section">Developers documentation </link> of LibVIDEO here.


Stream control
--------------

When playing videos, you surely want to halt, rewind, forward the video
stream. The family word for that is "stream manipulation" or "stream
control". Obviously, you need control over the data stream. LibGPF
provides everything you need, if stream manipulation is possible. What
does that mean? Well, when you play an AVI, Quicktime or a DVD video, then
that's possible, because those videos are stored contents. But a video
conference, for example, is an endless data-stream, so you can't do that
(except you store the video on disk and replay it because it's a stored
content then).

Note: Have a look into <link to "The API section">The API</link> section,
if you have questions about the functions and/or their syntax mentioned
here.

Using Flags you can control the behaviour of the whole API. That makes the
API much more powerful than it looks like to be.

The data transfer can be stopped and restarted again and again.
gpfStartStream() starts always from the beginning, when the
GPF_FRAME_FIRST flag is set, or continues from the last frame being
transfered, when GPF_FRAME_CONTINUE is set (= default).

gpfStopStream() is the way to stop an transfer. If you don't do a tranfer
from a stored content, then calling gpfStopStream() is the only way to do
that.

gpfTransferNextStreamBlock() has no effect, when transfer isn't started.
After creating a handle, you have to call gpfStartStream() explicitely, as
the transfer is stopped by default. The reason is, that you might set some
properties before. It may also skip some data, when the GPF_FRAME_MAYSKIP
flag is set and the transfer rate is set too high set by
gpfSetTransferrate.

BTW: When you want to set GPF_FRAME_MAYSKIP | GPF_PIPE_REALTIME, you should
know what you're doing, because gpfSetTransferRate() won't fail anymore.


Realtime aspects
----------------

LibGPF chooses the fastest pipeline per default automatically. When the
data transfer is stopped, gpfGetTransferRate() gives you the transfer
rate, LibGPF can guarantee you absolutely. During the data transfer, it
gives you either the _current_ transfer rate at calling time or the
average transfer rate, depending on how the flags are set by gpf*Flags().
With gpfSetTransferRate(), you can determine a certain transfer rate. It
fails, when LibGPF can't guarantee you the given one. If LibGPF can
guarantee an even higher rate, then it is slowed down automatically, when
the GPF_RATE_FIXED flag is set. When this flag is set without setting a
certain transfer rate, then the default behaviour is to take the transfer
rate LibGPF can guarantee.

If gpfSetTransferRate() fails, and you're sure, that both your hardware
and your OS can guarantee the transfer rate you want/need, then please
contact <link to "http://www.ggi-project.org/contact.php">the GGI
developer team</link> and we will find a solution together. TNX.


Thread safety
-------------

LibGPF doesn't support threads directly. But it is threadsafe in the
sense, so that using threads in applications won't hurt.



The API
=======


NAME
gpfInit, gpfExit - Initialize and uninitialize LibGPF

SYNOPSIS
#include <ggi/gpf.h>

int gpfInit(void);
int gpfExit(void);

DESCRIPTION
gpfInit initalizes the library. This function must be
called before using other LibGPF functions; otherwise the
results will be undefined.

gpfExit uninitializes the library (after being initalized
by gpfInit) and automatically cleanup if necessary. This
should be called after an application is finished with the
library. If any GPF functions are called after the library
has been uninitialized, the results will be undefined.

gpfInit allows multiple invocations. A reference count is
maintained, and to completely uninitialize the library,
gpfExit must be called as many times as ggiInit has been
called beforehand.

RETURN VALUE
gpfInit returns 0 for OK, otherwise an error code.

gpfExit returns:

0after successfully cleaning up,

> 0the number of 'open' gpfInit calls, if there has
been more than one call to gpfInit. As gpfInit and
gpfExit must be used in properly nested pairs, e.g.
the first gpfExit after two gpfInits will return 1.

< 0error, especially if more gpfExit calls have been
done than gpfInit calls.

EXAMPLE
INITIALIZE AND UNINITIALIZE LIBGPF
if (gpfInit() < 0) {
fprintf(stderr, "Cannot initalize LibGPF!\n");
exit(1);
}/* if */

/* Do some LibGPF stuff */

gpfExit();

---------------------------------------------------------------------------

NAME
gpfOpen, gpfOpenInput, gpfOpenOutput, gpfClose, gpfCloseInput,
gpfCloseOutput - open and close a handle

SYNOPSIS

#include <ggi/gpf.h>

gpf_handle_t gpfOpen(char *input, char *inputargf,
 char *output, char *outputargf, ...);

int gpfOpenInput(gpf_handle_t *handle, char *input, char *argf, ...);
int gpfOpenOutput(gpf_handle_t *handle, char *output, char *argf, ...);

int gpfClose(gpf_handle_t handle);

int gpfCloseInput(gpf_handle_t handle);
int gpfCloseOutput(gpf_handle_t handle);

int gpfIsOpened(gpf_handle_t handle, int type);

DESCRIPTION

gpfOpen opens a handle. The handle is specified by four
strings. The first one specifies the input target,
followed by its parameters (second string). The third
string specifies the output target, followed by its
parameters (fourth string). The parameters are absolutely
target specific, so see the target doc for more information.

The other arguments are intended to pass some non-textual
information to the targets. They are specified in the
parameters (printf style).

gpfOpenInput is likely gpfOpen, but only opens the specified
input target. When handle is NULL, then gpfOpenInput opens
a new handle additionally. Otherwise, it assumes the handle
is valid.

gpfOpenOutput is the analogue of gpfOpenInput.

gpfClose closes all targets, pipes and protocols cleanly.
The stream transfer must be stopped before, otherwise it does
absolutely nothing.

gpfCloseInput only closes the input target plus the protocol
it used. When the output is already closed, then it closes
everything, that was left open. The stream transfer must be
stopped before, otherwise it does absolutely nothing.

gpfCloseOutput is the analogue of gpfCloseInput.

RETURN VALUE
gpfOpen, gpfOpenInput, gpfOpenOutput returns the opened handle
(gpf_handle_t), or NULL for error.

gpfClose, gpfCloseInput, gpfCloseOutput returns 0 for OK,
otherwise an error code.

EXAMPLE
1. Example how to load an avi-video from an webserver through a proxy
into a libbuf object, where a gstreamer avi-plugin is used to do this:

ggiBuf_t buf = ggiBufCreateBuffer(vis, ...);
 ...
handle = gpfOpen("gstreamer",
	"www-proxy://www.avi-collection.org/intro.avi -plugin=avi",
"libbuf", "-buf=%p", buf);

2. Example how to load a gif-picture from local disk into a GGI visual:

vis = ggiOpen("memory");
 ...
handle = gpfOpen("gif",
"file:/usr/local/ftp/pub/picture-collection/GGI_logo.gif",
"libggi", "-vis=%p", vis);

3. Example how to save a jpg-picture from GGI visual
to a remote ftpserver.

handle = gpfOpen("libggi","-vis=%p",
"jpg", "ftp://ftp.picture-collection.org/pub/image.jpg",
vis);

SEE ALSO
gpfInit(3)

---------------------------------------------------------------------------

NAME
    gpfPipelineSetup - function for setting up the pipeline

SYNOPSIS
#include <ggi/gpf.h>

    int gpfPipelineSetup(gpf_handle_t handle);

DESCRIPTION
    gpfPipelineSetup creates the pipeline being used for the transfer.
    It has to be called explicitely after gpfOpen() in order to allow
    the user to set some properties for the pipeline. The gpfClose()
    function family is the only way to shutdown the pipeline cleanly.

RETURN VALUE
gpfPipelineSetup returns 0 for OK, otherwise an error code.

EXAMPLE

SEE ALSO
    gpfOpen(3), gpfSetProperties(3)

---------------------------------------------------------------------------

NAME
gpfSetProperties, gpfGetProperties, gpfClearProperties - functions for
pipline control

SYNOPSIS
#include <ggi/gpf.h>

int gpfSetProperties(gpf_handle_t handle, const struct gpf_properties_t *props);
int gpfGetProperties(gpf_handle_t handle, struct gpf_properties_t *props);
int gpfClearProperties(struct gpf_properties_t *props);

DESCRIPTION
gpfSetProperties is used to give the user control over the
    pipeline, beginning from the pipeline selection through the
    pipeline management to setting the transfer rate and other
    things. Note, that changing properties only affects, when
    the data stream is stopped at calling time.

gpfGetProperties returns you the current properties being
    active on a handle.

gpfClearProperties initializes the property variable and
    sets the value for the default behaviour of libgpf.

RETURN VALUE
gpfSetProperties return 0 for OK, otherwise an error code.

gpfGetProperties gpfClearProperties returns 0 for OK, otherwise
an error code.

EXAMPLE
struct gpf_properties_t props;

...

    /* clear properties */
gpfClearProperties(&props);

    /* set a flag */
    gpfSetFlag(&props, GPF_FRAME_FIRST);

    /* set properties */
    gpfSetProperties(&props);

SEE ALSO
gpfSetFlags(3), gpfSetTransferRate(3)

---------------------------------------------------------------------------

NAME
gpfSetCurrFrame, gpfGetCurrFrame, gpfGetNumFrames - functions for
stream control

SYNOPSIS
#include <ggi/gpf.h>

int gpfSetCurrFrame(gpf_handle_t handle, int num_frame);
int gpfGetCurrFrame(gpf_handle_t handle, int *num_frame);
int gpfGetNumFrames(gpf_handle_t handle, int *num_frames);

DESCRIPTION
gpfSetCurrFrame sets a certain frame number of the stream
being transfered next time, when gpfTransferNextStreamBlock()
is called.

gpfGetCurrFrame returns you the current frame number being
transfered next time.

gpfGetNumFrames returns, how much frames the video, animated
gif or what-ever-else consists of, counted from one.

RETURN VALUE
gpfSetCurrFrame return 0 for OK, otherwise an error code
(i.e. when the given frame number is out of range).

gpfGetCurrFrame, gpfGetNumFrames returns 0 for OK, otherwise
an error code.

EXAMPLE
int curr_frame;

...

gpfGetCurrFrame(handle, &curr_frame);

/* skip five frames */
gpfSetCurrFrame(handle, curr_frame + 5);

SEE ALSO
gpfStartStream(3), gpfSetFlags(3)

---------------------------------------------------------------------------

NAME
gpfSetFlags, gpfGetFlags - Set or get flags affecting operation on
a handle

SYNOPSIS
#include <ggi/gpf.h>

enum gpf_flags gpfSetFlags(struct gpf_properties_t *props, enum gpf_flags flags);
enum gpf_flags gpfGetFlags(const struct gpf_properties_t *props);

enum gpf_flags gpfAddFlags(struct gpf_properties_t *props, enum gpf_flags flags);
enum gpf_flags gpfRemoveFlags(struct gpf_properties_t *props, enum gpf_flags flags);

DESCRIPTION

gpfSetFlags sets the specified flags (bitwise OR'd together)
in the properties of a handle. This function is used to control
    the behaviour of stream operation functions like gpfStartStream.

gpfGetFlags obtains the flags currently in effect.

gpfAddFlags and gpfRemoveFlags are macros that set or
unsets the specified flags.

RETURN VALUE
gpfSetFlags, gpfAddFlags, gpfRemoveFlags returns the flags
previously set.

gpfGetFlags returns the flags currently set.

EXAMPLE
STOP THE STREAM AND RESTART FROM THE BEGINNING
gpfStopStream(handle);

    gpfGetProperties(handle, &props);
gpfAddFlags(&props, GPF_FIRST_FRAME);
    gpfSetProperties(handle, &props);

gpfStartStream(handle);

SEE ALSO
gpfStartStream(3), gpfSetCurrFrame(3)

---------------------------------------------------------------------------

NAME
gpfStartStream, gpfStopStream, gpfTransferNextStreamBlock,
gpfIsStopped, gpfIsEOS - starts and stops the stream transfer

SYNOPSIS
#include <ggi/gpf.h>

typedef void (*gpfIndicatorCallback)(int current, int total);

int gpfStartStream(gpf_handle_t handle);
int gpfStopStream(gpf_handle_t handle);

int gpfTransferNextStreamBlock(gpf_handle_t handle,
gpfIndicatorCallback cbI);

int gpfIsStopped(gpf_handle_t handle);
int gpfIsEOS(gpf_handle_t handle);

DESCRIPTION
gpfStartStream starts the stream transfer.

gpfStopStream stops the stream transfer.

gpfTransferNextStreamBlock performs the next data-transfer,
when it was started before, otherwise this function has no effect.
The block size is internally optimized. It may change, each time
called (i.e. when the data stream goes over a network and the
transfer rate changes more or less fast). Internally, it
increments the current frame number automatically.

gpfIsStopped obtains the status currently in effect.

gpfIsEOS tests the End Of Stream indicator of a stream similar to
the feof() function of the c standard library.
WARNING: Only use it on stored contents, otherwise you are
dangered to run into an endless loop.

RETURN VALUE
gpfStartStream, gpfStopStream, gpfTransferNextStreamBlock returns
0 for OK, otherwise an error code.

gpfIsStopped returns true, if transfer is stopped.

gpfIsEOS returns true, when the End Of Stream is reached.

EXAMPLE
TRANSFER OF A STORED CONTENT
gpfStartStream(handle);

while (!gpfIsStopped(handle)) {
gpfTransferNextStreamBlock(handle, NULL);
}/* while */

/* Close handle */
gpfClose(handle);

SEE ALSO
gpfSetFlags(3), gpfSetCurrFrame(3)

---------------------------------------------------------------------------

NAME
gpfSetTransferRate, gpfSetTransferRate - realtime aspects

SYNOPSIS
#include <ggi/gpf.h>

int gpfSetTransferRate(struct gpf_properties_t *props, int rate);
int gpfGetTransferRate(gpf_handle_t handle);

DESCRIPTION
gpfSetTransferRate sets the transfer rate you wish you to have.
Default unit is KBytes/s, but KBit/s is used, when GPF_RATE_KBIT
flag is set. It gives you some control to realtime streaming.

gpfGetTransferRate obtains the transfer rate LibGPF uses.

RETURN VALUE
gpfSetTransferRate returns 0 for OK, otherwise an error code.

gpfGetTransferRate returns the transfer rate in the
specified unit.

EXAMPLE
SET A CONSTANT TRANSFER RATE, YOU CAN GET ABSOLUTELY SURELY
rate = gpfGetTransferRate(handle);

    gpfGetProperties(&props);
gpfAddFlags(handle, GPF_RATE_FIXED | GPF_PIPE_REALTIME);

gpfSetTransferRate(&props, rate);
    gpfSetProperties(&props);

gpfStartStream(handle);

SEE ALSO
gpfSetCurrFrame(3), gpfStartStream(3), gpfSetFlags(3)





Developer documentation
=======================


I assume here, that you already read the <link to "Introduction
section">Introduction</link> and <link to "The big picture section">the
big picture</link> section. Thus, I assume here, that you have a rough
overview how things _might_ work internally.

In this section, you should get a detailed picture about what's going on
when a handle is opened, how the pipeline management works and what's
actually going on, when a transfer happens. This should give you a deep
understanding how things work and why you should do what, when you write a
target, protocol or pipe sublib.


How gpfOpen() works
-------------------

This function has actually nothing to do with the transfer itself, but
nonetheless, it is the most complex one after gpfPipelineSetup().
gpfOpen() opens the two targets and the protocols. gpfOpen() fails, when
opening any sublib fails.

gpfOpen() is part of LibGPF's base lib. Its files are in the libgpf/gpf
subdirectory. You can find it in the init.c file. You will be surprised
how easily this function is realized.

First it calls vgpfOpenInput(), which opens the input target and its
protocol. Then it calls vgpfOpenOutput(), which does the same for the
output side.

vgpfOpenInput() creates a handle, if handle was a NULL pointer. That is
usually the case. If the target was already opened before, then it fails
to avoid to reopen the input target, which would cause problems. Then it
opens the target and its protocol using _gpfOpenTarget(). When
_gpfOpenTarget() fails, then vgpfOpenInput() fails as well. Then it
initializes some mutexes to make libgpf threadsafe and exits.

vgpfOpenOutput() works analogous to vgpfOpenInput(), so I don't go in
further.

In the internal.c file _gpfOpenTarget() is implemented as well as the
following functions. At first, _gpfOpenTarget() allocates enough memory
for the target handler. Then a pipeline element is created, which holds
some administration information for the target handler. This is necessary
to not overwrite/destroy these information, when pipelines gets
created/destroyed in the pipeline management and to simplify the pipeline
cloning process - but that only on the side. More about that comes <link
to "The pipeline management section">later</link>. Then
_gpfParseProtocol() filters out all information needed to open the
protocol with the right parameters. _gpfOpenDL() finally opens the target.
Then _gpfOpenProtocol() loads the protocol and _gpfOpenTarget() returns.

_gpfOpenProtocol() works very similar to the _gpfOpenTarget() function. At
first it allocates enough memory for the protocol handler. Then it creates
one pipeline element to hold the administration information. The reason is
the same as for _gpfOpenTarget(). Finally, _gpfOpenDL() opens the protocol
and _gpfOpenProtocol() exits.



The pipeline management
-----------------------

The pipeline management is part of LibGPF's base lib. Its files are in the
libgpf/gpf subdirectory. The pipeline management is in the pipe_mgr.c
file. The pipe.c file contains some helper routines.

After <link to "How does gpfOpen() work section">gpfOpen()</link> opened
_both_ the input and the output targets are successfully _inside_ of the
<link to "gpfOpen() in the API section">gpfOpen()</link> function, the
user called _gpfPipelineSetup().

gpfPipelineSetup() sets up the pipeline to make the transfer possible and
fails, when no proper pipeline was found. It tries to find the optimal
pipeline using _gpfPipelineFindOptimal(), that fits best to the <link to
"gpfSetProperties() in the API section">wishes of the user</link>. Then it
initializes the pipeline by calling the setup function pointer of each
pipeline element. This gives the target and pipe sublibs the chance to do
things, they can't do at GGIopen time because of lack of information about
the pipeline itself. After initizialization, all the pipeline
administration information get destroyed, which is of no longer use to
safe memory. _gpfPipelineSetup() fails, when _gpfPipelineFindOptimal()
fail. _gpfPipelineSetup() will return back to gpfOpen() and gpfOpen()
finally exits.

_gpfPipelineFindOptimal() searches for as many pipelines as possible at
first. It uses the _gpfPipelineFind() function for this. The found
pipelines are formed in a line using a dynamic one way linked list. Then
it selects the pipeline using _gpfPipelineSelect(), that fits best to the
<link to "gpfSetProperties() in the API section">users wishes</link>. Then
it destroys all other pipelines and returns the selected one.

_gpfPipelineFind()'s job is to find one (more) pipeline, which surely
brings the result the user wanna have _without_ taking care, whether the
pipeline itself fits to the user's wishes or not. I guess, you can think
of why!? If it creates the _first_ pipeline, then it gets the information
lists of the two targets. These lists describes all pixel/vector/video
formats, the target can handle. Each list is a dynamic one way linked
list. Why not a static linked list is used? That's simple: That
_gpfPipelineSetup() is able to remove them before returning. Then it
creates clones of the targets and links them together to a first
pre-liminary pipeline. This clone-process is necessary to avoid, that
_gpfPipelineFindOptimal() closes the targets accidentally, when destroying
the non-selected pipelines. If _gpfPipelineFind() tries to find another
pipeline, then it should obviously be made sure, that each found pipeline
is kept unique. Therefore it needs information, where to continue. For
this case, it is good, that each pipeline item contains state-information
(The "mark" field in the gpf_pipeline_item_t structure). Thus, simply
cloning the whole pipeline, that was found before, gives all needed
information. Then _gpfPipelineFind() goes through the (pre-liminary)
pipeline and checks, if each pipeline item fits with the following one.
_gpfPipelineItemFits() is used for this. If yes, then it compares the next
one with the overnext one, etc. If no, then _gpfPipelineFindSuitableItem()
searches for an appropriate pipeline element. _gpfPipelineIsUnique() makes
sure, that the pipeline is still unique. Otherwise another pipeline item
must be found. All the mentioned functions uses the state information in
order to make sure to continue the searching for another pipeline. Once
the whole pipeline is checked through, it will be returned to
_gpfPipelineFindOptimal() or NULL, when no pipeline could be found.

_gpfPipelineSelect() goes through each pipeline, found by
_gpfPipelineFind(). It accumulates the memory usage and the runtime of
each pipeline item. Then these values are compared with the values of the
other pipelines. The pipeline, which values fits best to the ratio of
speed<->memory given by the user, is the happiest one and will be returned
to _gpfPipelineFindOptimal().



The transfer (overview)
-----------------------

The transfer happens within gpfTransferNextStreamBlock() and is part of
LibGPF's base lib, too. It is in the gpf.c file.

In this stage, the pipeline is already initialized. That means, the
pipeline knows the size (width/height) of the _input_ stream (from the
input target) and each pipeline item has its own input buffer allocated -
where it reads its data from. Where the data goes to is the output buffer.
That is simply the input buffer of the next pipeline item.

gpfTransferNextStreamBlock() is the function, that performs the transfer
asyncronously to the application. At first, it calculates how much bytes
to try to transfer at, to do realtime transfer. The calculation is done
in dependence to the time the last transfer took. Then it calls the 
handle->pipe_index->transfernextstreamblock function pointer, which reads
the data from the input buffer, converts and writes it to the output buffer,
until a good amount of the calculates bytes are transfered.

During that operation, an internal transfer counter is updated and a
"callback Indicator" will be called, if non-NULL. The callback Indicator
is a function pointer set by the user.
Then it gives control back to the application.

The transfernextstreamblock callback function returns the bytes being
transfered. Negative values indicates an occured error. The
handle->pipe_index points _always_ to the current pipeline item. It is
required that that is static data in order to be able to continue the data
transfer later, when the transfer (was) stopped by any reason.

In case not all data couldn't be transfered for some reasons, the data is
cached inside each pipleline item. The cached data will be prepended to
the new data and then all the data will be transfered as usual. The
transfernextstreamblock callback function also sets the handle->pipe_index
function to the next pipeline item that has to transfer some data.

After finishing the loop, the whole transfer should be finished and the
transfer is stopped.

That is actually everything what happens in the base lib. Everything else
is handled inside the target/pipe sublib. So open questions should be
answered there. Read on.



The transfer - in a target sublib
---------------------------------

Here we have to distinguish two cases: a) when the target acts as the
input and b) when the target acts as the output.

a) In the setup routine, it allocates as much memory as needed for both
the input and the output buffer to read the data from the protocol,
convert it - if needed/required - and to write it to the output.

transfernextstreamblock reads the data from the protocol using the
handle->opio_input->read function pointer, appends the data to the cached
data, if present, and writes the data to the output buffer using the
handle->pipeline->write function pointer. Between these two io operations,
the conversion is performed, if needed/necessary. Where and how the
conversion actually happens differs from target to target. The way of
implementation is fully up to the target developer(s). Then the
handle->pipe_index is set to the next pipeline element, that has to
transfer the next data. It is _not_ necessarily the next level pipeline
item. If the current one has still data to transfer, but the transfer
limit is archieved, then the handle->pipe_index is _not_ changed.

b) In the setup routine, it can assume that the input buffer is non-NULL
and that the size is correct. Only the output buffer may be resized to be
able to write the data through the protocol - also after the conversion,
if performed.

transfernextstreamblock reads the data from the input buffer through the
handle->pipeline->read function pointer and writes the data through the
protocol using the handle->opio_output->write function pointer. Where and
how the conversion happens is the same as in a). When there's no more data
to transfer, then handle->pipe_index is set to the previous pipeline
element to get new data to read.



The transfer - in a pipe sublib
-------------------------------

In the setup routine, it can assumed that the input buffer is non-NULL and
that the size is correct. Only the output buffer may be resized to be able
to write the converted data to the output buffer. Further, everything gets
finally prepared to get the conversion working (setting up the bitshifts,
etc...).

transfernextstreamblock reads the data from the input buffer through the
handle->pipeline->read function pointer, performs the data conversion and
writes the data to the output buffer using the handle->pipeline->write
function pointer. Where and how the conversion happens is fully up to the
pipe sublib developer(s).

When the transfer limit is archieved, but there's still data to transfer,
then handle->pipe_index is _not_ changed. Otherwise it is set to the next
pipeline item, if it wasn't the last active one before the current. In
that case the previous pipeline item is selected, in order to get new
data.



The internal data structure
---------------------------


Structure: gpf_handle

Field: props
Description: Contains the properties of the pipeline

Field: opio_input
Description: Pointer to the input target handle

Field: opio_output
Description: Pointer to the output target handle

Field: pipeline
Description: Pointer to a double linked list, which contains all pipeline
elements.

Field: pipe_index
Description: Pointer to the pipeline element, that will be processed next
time by transfernextstreamblock




Structure: gpf_handle_opio_t

Field: head
Description: Used by the dl handling. non-baselib hackers never will need
it.

Field: opproto
Description: Pointer to the protocol handle.

Field: pipe
Description: Pointer to the pipe-element. It is used to abstract the
target as a pipe element. This simplifies the pipeline management and the
pipeline transfer a lot.

Field: open
Description: Used to tell the protocol to open the
file/connection/whatever it is used for. Then the target can perform some
preparations to read/write the data from/to the protocol in the right
format later. Note, that it is not known, which format the next pipeline
element expects at that time. This function pointer has to be initialized
in the targets' GPFopen() function. It is called in the _gpfOpenTarget()
function after the target and its protocol was opened.

Field: close
Description: Used to tell the protocol to close the
file/connection/whatever it is used for. This function pointer has to be
initialized in the target' GPFopen() function, too. It is called in the
_gpfCloseTarget() function before closing the target and its protocol.
This allows the target to shutdown cleanly.

Field: setcurrframe
Description: This function pointer has to be initizialized in the targets'
GPFopen() function. It allows to handle multi-image formats (i.e. animated
gif/jpeg or videos). If the target handles no multi-image format, then it
should fail on any given value, but 1. This function pointer is used to
give the information from the gpfSetCurrFrame() API function through to
the target.

Field: getcurrframe
Description: This function pointer has to be initialized in the target's
GPFopen() function. It always returns the current frame being worked on.
On non-multi image formats, this is always 1.

Field: getnumframes
Description: This function pointer has to be initialized in the target's
GPFopen() function. It returns the number of available frames. On
non-multi image formats, this is always 1.

Field: read
Description: This function pointer has to be initialized in the target's
GPFopen() function. It is used to read the data coming from the protocol.

Field: write
Description: This function pointer has to be initialized in the target's
GPFopen() function. It is used to write the data to the protocol.

Field: doio
Description: If a target provides its own mechanism to read/write the data
from/to the protocol, then it will use the so called target protocol. This
is usually the case, when the target uses another lib. The target protocol
calls the doio function pointer, where the open, close, setcurrframe,
getcurrframe, getnumframes, read and write function pointers get
initialized. If the target uses the target protocol, then this doio
function pointer has to be initialized in the target's GPFopen() function.
Otherwise, doio should be NULL.

Field: pipe_getinfolist
Description: This function pointer has to be initizalized in the target's
GPFopen() function. It is used to create a one way linked list, which
contains all information about the formats the target can handle. It is
called by the pipeline management.




Structure: gpf_handle_opproto_t

Field: head
Description: Used by the dl handling. non-baselib hackers never will need
it.

Field: pipe
Description: Pointer to the pipe-structure. It is used to simplify the dl
handling between the target and the protocol.

Field: open
Description: This function pointer has to be initialized in the protocol's
GPFopen() function. It is used to open the file/connection/whatever the
physical thing is. This function pointer is called by its targets' open
function.

Field: close
Description: This function pointer has to be initialized in the protocol's
GPFopen() function. It is used to close the file/connection/whatever the
physical thing is. This function pointer is called by its targets' close
function.

Field: read
Description: This function pointer has to be initialized in the protocol's
GPFopen() function. It is used to read a certain size of the data-stream.
It also takes care about caching and such. This function pointer is called
by its targets' read function.

Field: write
Description: This function pointer has to be initialized in the protocol's
GPFopen() function. It is used to write a certain size to the data-stream.
It also takes care about caching and such. This function pointer is called
by its targets' write function.

Field: lseek
Description: This function pointer has to be initialized in the protocol's
GPFopen() function. It is used for the same reason as libc's lseek()
function. It has also the same behaviour and flags.

Field: flush
Description: This function pointer has to be initialized in the protocol's
GPFopen() function. It is used to force a flush of the protocol's cached
data.

Field: eos
Description: This function pointer has to be initialized in the protocol's
GPFopen() function. It works similar to the feof() libc function. It
returns true, if a End Of Stream (EOS) is determined. Note, that this
function will always fail on endless data-streams (i.e. video-streaming).

Field: protopriv
Description: Used to store protocol specific data





Structure: gpf_pipeline_item_t

Field: head
Description: Used by the dl handling. non-baselib hackers never will need
it.

Field: dlhandle
Description: Used by the dl handling. non-baselib hackers never will need
it.

Field: prev
Description: The previous pipe element

Field: next
Description: The next pipe element

Field: input_head
Description: single linked list containing the formats, that the pipe
accepts from the previous pipe element.

Field: output_head
Description: single linked list containing the formats, that the pipe
offers to the next pipeline.

Field: mark
Description: Used by the pipeline management to mark the last successful
place. The pipe sublibs' uses it to read out the information of the
selected format, which are stored in the infolists.

Field: setup
Description: This function pointer has to be initialized in the sublibs'
GPFopen() function. This function pointer is called by the pipeline
management. At that time, the sublib knows the previous/next pipe and
therefore knows, which format it gets and how the conversion has to happen
to get the right format for the next pipe.

Field: getinfolist
Description: This function pointer is unused on targets. On real pipe
sublibs, it has to be initialized in the pipe's GPFopen() function. It
creates a single linked list containing all formats the pipe sublib can
handle. It is called by the pipeline management.

Field: getformat
Description: This function pointer has to be initialized in the sublibs'
GPFopen() function (both target and pipe sublib). This function pointer
is called by the pipeline management. At that time, the pipeline is under
creation. When this sublib uses the GPF_PIPE_DF_ALLVARIANTS flag, then
it has obviously to know, which formats it has to expect from the input
and output. At the calling time, it has to copy all the information from
the previous and next sublib in the pipeline. When this sublib don't use
GPF_PIPE_DF_ALLVARIANTS at all, then this function should be empty, but
returung GGI_OK.

Field: transfernextstreamblock
Description: This function pointer has to be initialized in the sublibs'
GPFopen() function. This function is called by
gpfTransferNextStreamBlock().




Structure: gpf_pipeline_t

Field: head
Description: Used by the dl handling. non-baselib hackers never will need
it.

Field: next
Description: Pointer to the next pipeline.

Field: item_head
Description: Pointer to the first pipeline element. This has to be a
target.

Field: read
Description: This function pointer points to a default function. It reads
the data from buf[read_buf] and writes it to buf_conv. This function is
used to simplify the transfer/conversion implementation in the sublibs.

Field: write
Description: This function pointer points to a default function. It reads
the data from buf_conv and writes it to buf[write_buf]. This function is
used to simplify the transfer/conversion implementation in the sublibs.

Field: width
Description: Width of the image in pixels. 

Field: height
Description: Height of the image in pixels.





Developer guide
===============


I assume here, that you read the <link to "Developer Documentation
section">Developer Documentation</link>. Thus, I assume here, that you
have a detailed understanding how things _does_ work internally.


Writing a target sublib
-----------------------

All targets are in the libgpf/io directory, so change to there.

Step 1 is to build a compiling framework. At first copy the sample target
(don't forget the hidden .cvsignore file :) and rename it to your target's
name. If you don't use a library providing its own methods to access the
physical data, then you should remove the proto.c file.

Next go through all the files and replace the <sample> tag by the target's
name. Then go through all files and read the #warnings and comments in the
source and follow them.

Next, add your target's name in the libgpf/io/Makefile.am file (line
DIST_SUBDIRS). Then add your target to the libgpf/configure.in file. There
are already some sample blocks (search for "sample"). Copy and change them
appropriatly to your target. Further add some missing headers to
AC_CHECK_HEADERS and add missing AC_CHECK_LIB entries. Don't forget to
disable your target, if something (header and/or lib) is missing in the
following if-blocks of AC_CHECK_LIB. Add your own if-block(s), if you
added a header to AC_CHECK_HEADERS. Now type autogen.sh and configure on
the libgpf root directory to make your above changes visible.

Step 2 is to finish your target. Rebuild libgpf with your target enabled.
Fix the compiler warnings and errors and fill in your code in the gaps.

Step 3: Test your target and go back to Step 2 until your target works.



Writing a protocol sublib
-------------------------

All protocols are in the libgpf/proto directory, so change to there.

Step 1 is to build a compiling framework. At first copy the sample
protocol (don't forget the hidden .cvsignore file :) and rename it to your
protocol's name.

Next go through all the files and replace the <sample> tag by the
protocol's name. Then go through all files and read the #warnings and
comments in the source and follow them.

Next, add your protocol's name in the libgpf/proto/Makefile.am file (line
DIST_SUBDIRS). Then add your target to the libgpf/configure.in file. There
are already some sample blocks (search for "sample"). Copy and change them
appropriatly to your protocol. Further add some missing headers to
AC_CHECK_HEADERS and add missing AC_CHECK_LIB entries. Don't forget to
disable your protocol, if something (header and/or lib) is missing in the
following if-blocks of AC_CHECK_LIB. Add your own if-block(s), if you
added a header to AC_CHECK_HEADERS. Now type autogen.sh and configure on
the libgpf root directory to make your above changes visible.

Step 2 is to finish your protocol. Rebuild libgpf with your protocol
enabled.  Fix the compiler warnings and errors and fill in your code in
the gaps.

Step 3: Test your protocol and go back to Step 2 until your protocol
works.



Writing a pipe sublib
---------------------

All pipes are in the libgpf/pipe directory, so change to there.

Step 1 is to build a compiling framework. At first copy the sample pipe
(don't forget the hidden .cvsignore file :) and rename it to your pipe's
name.

Next go through all the files and replace the <sample> tag by the pipe's
name. Then go through all files and read the #warnings and comments in the
source and follow them.

Next, add your target's name in the libgpf/pipe/Makefile.am file (line
DIST_SUBDIRS). Then add your target to the libgpf/configure.in file. There
are already some sample blocks (search for "sample"). Copy and change them
appropriatly to your target. Further add some missing headers to
AC_CHECK_HEADERS and add missing AC_CHECK_LIB entries. Don't forget to
disable your pipe, if something (header and/or lib) is missing in the
following if-blocks of AC_CHECK_LIB. Add your own if-block(s), if you
added a header to AC_CHECK_HEADERS. Now type autogen.sh and configure on
the libgpf root directory to make your above changes visible.

Step 2 is to finish your target. Rebuild libgpf with your pipe enabled.
Fix the compiler warnings and errors and fill in your code in the gaps.

Step 3: Test your pipe and go back to Step 2 until your pipe works.





<!-- Ignore this stuff

Have a look at <link to
"ftp://ftp.inria.fr/INRIA/Projects/algo/web/seminars/sem92-93/flajolet.ps">
ftp://ftp.inria.fr/INRIA/Projects/algo/web/seminars/sem92-93/flajolet.ps
</link> and <link to
"http://mitpress.mit.edu/catalog/item/default.asp?ttype=2&tid=8570">
http://mitpress.mit.edu/catalog/item/default.asp?ttype=2&tid=8570</link>
for detailed information to get a feeling, how fast a algorithm might be.


Pipeline:
Yes, cpu/speed is tricky. The right way would be a way to autodetect the
CPU at initialization time of libgpf using libgg.

-->

