From 60239a297dd0e7a6454dbd15de9d0186dea93c14 Mon Sep 17 00:00:00 2001 From: Michael Roitzsch Date: Sun, 12 Oct 2003 19:06:43 +0000 Subject: updated hackersguide: * added some small pieces * incorporated some former READMEs and Mike's fabulous demuxer and decoder doc * splitted into multiple files * made new drawings (hopefully I am not the only one to understand them) * Makefile support for building a HTML version which is now installed in the user's doc directory CVS patchset: 5494 CVS date: 2003/10/12 19:06:43 --- doc/hackersguide/internals.sgml | 759 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 759 insertions(+) create mode 100644 doc/hackersguide/internals.sgml (limited to 'doc/hackersguide/internals.sgml') diff --git a/doc/hackersguide/internals.sgml b/doc/hackersguide/internals.sgml new file mode 100644 index 000000000..b424b7459 --- /dev/null +++ b/doc/hackersguide/internals.sgml @@ -0,0 +1,759 @@ + + xine internals + + + Engine architecture and data flow + + + + + + + + + xine engine architecture + + + + Media streams usually consist of audio and video data multiplexed + into one bitstream in the so-called system-layer (e.g. AVI, Quicktime or MPEG). + A demuxer plugin is used to parse the system layer and extract audio and video + packages. The demuxer uses an input plugin to read the data and stores it + in pre-allocated buffers from the global buffer pool. + The buffers are then added to the audio or video stream fifo. + + + From the other end of these fifos the audio and video decoder threads + consume the buffers and hand them over to the current audio or video + decoder plugin for decompression. These plugins then send the decoded + data to the output layer. The buffer holding the encoded + data is no longer needed and thus released to the global buffer pool. + + + In the output layer, the video frames and audio samples pass through a + post plugin tree, which can apply effects or other operations to the data. + When reaching the output loops, frames and samples are enqueued to be + displayed, when the presentation time has arrived. + + + A set of extra information travels with the data. Starting at the input and + demuxer level, where this information is generated, the data is attached to + the buffers as they wait in the fifo. The decoder loops copy the data to + a storage of their own. From there, every frame and audio buffer leaving + the stream layer is tagged with the data the decoder loop storage currently + holds. + + + + + Plugin system + + The plugin system enables some of xine's most valuable features: + + + + drop-in extensiability + + + + + support parallel installation of multiple (incompatible) libxine versions + + + + + support for multiple plugin directories + ($prefix/lib/xine/plugins, + $HOME/.xine/plugins, ...) + + + + + support for recursive plugin directories + (plugins are found even in subdirectories of the plugin directories) + + + + + version management + (On start, xine finds all plugins in its plugin (sub)directories and + chooses an appropriate version (usually the newest) for each plugin.) + + + + + simplification + (Plugins don't have to follow any special naming convention, + and any plugin may contain an arbitrary subset of input, demuxer, + decoder or output plugins.) + + + + + + Essentally, plugins are just shared objects, ie dynamic libraries. In + contrast to normal dynamic libraries, they are stored outside of the + system's library PATHs and libxine does its own bookkeeping, which + enables most advanced features mentioned above. + + + Plugin location and filesystem layout + + The primary goal for this new plugin mechanism was the need to support + simultaneous installation of several (most likely incompatible) + libxine versions without them overwriting each other's + plugins. Therefore, we have this simple layout: + + + Plugins are installed below XINE_PLUGINDIR + (/usr/local/lib/xine/plugins by default). + Note that plugins are never directly installed into XINE_PLUGINDIR. + Instead, a separate subdirectory is created for each "plugin + provider". A plugin provider is equivalent with the exact version of + one source package. Typical examples include "xine-lib-0.9.11" or + "xine-vcdnav-1.0". Every source package is free to install an + arbitrary number of plugins in its own, private directory. If a + package installs several plugins, they may optionally be organized + further into subdirectories. + + + So you will finally end up with something like this: + +   /usr/local/lib/xine/plugins +    xine-lib-0.9.11 +    demux_mpeg_block.so +    decode_mpeg.so +    video_out_xv.so +    ... +    xine-vcdnav-0.9.11 +    input_vcdnav.so +    xine-lib-1.2 +    input +    file.so +    stdin_fifo.so +    vcd.so +    demuxers +    fli.so +    avi.so +    ... +    decoders +    ffmpeg.so +    mpeg.so (may contain mpeg 1/2 audio and video decoders) +    pcm.so +    ... +    output +    video_xv.so +    audio_oss.so +    ... +    xine-lib-3.0 +    avi.so (avi demuxer) +    mpeg.so (contains mpeg demuxers and audio/video decoders) +    video_out_xv.so (Xv video out) +    ... + + + As you can see, every package is free to organize plugins at will + below its own plugin provider directory. + Additionally, administrators may choose to put plugins directly into + XINE_PLUGINDIR, or in a "local" subdirectory. + Users may wish to put additional plugins in ~/.xine/plugins/. + Again, there may be subdirectories to help organize the plugins. + + + The default value for XINE_PLUGINDIR can be obtained using the + xine-config --plugindir command. + + + + Plugin Content: What's inside the .so? + + Each plugin library (.so file) contains an arbitrary number of (virtual) + plugins. Typically, it will contain exactly one plugin. However, it + may be useful to put a set of related plugins in one library, so they + can share common code. + + + First of all, what is a virtual plugin? + A virtual plugin is essentially a structure that is defined by the + xine engine. This structure typically contains lots of function + pointers to the actual API functions. + For each plugin API, there are several API versions, and each API + version may specify a new, incompatible structure. Therefore, it is + essential that only those plugins are loaded that support current + libxine's API, so the .so file needs a plugin list that + provides libxine with the version information, even before it tries to + load any of the plugins. + + + This plugin list is held in an array named xine_plugin_info": + +   plugin_info_t xine_plugin_info[] = { +    /* type, API, "name", version, special_info, init_function */ +    { PLUGIN_DEMUX, 20, "flac", XINE_VERSION_CODE, NULL, demux_flac_init_class }, +    { PLUGIN_AUDIO_DECODER, 13, "flacdec", XINE_VERSION_CODE, &dec_info_audio, init_plugin }, +    { PLUGIN_NONE, 0, "", 0, NULL, NULL } +   }; + + + The structure of xine_plugin_info may never be changed. + If it ever needs to be changed, it must be renamed to avoid + erraneous loading of incompatible plugins. + + + xine_plugin_info can contain any number of plugins + and must be terminated with a PLUGIN_NONE entry. Available plugin + types are: + +   #define PLUGIN_NONE 0 +   #define PLUGIN_INPUT 1 +   #define PLUGIN_DEMUX 2 +   #define PLUGIN_AUDIO_DECODER 3 +   #define PLUGIN_VIDEO_DECODER 4 +   #define PLUGIN_SPU_DECODER 5 +   #define PLUGIN_AUDIO_OUT 6 +   #define PLUGIN_VIDEO_OUT 7 +   #define PLUGIN_POST 8 + + + The plugin version number is generated from xine-lib's version number + like this: MAJOR * 10000 + MINOR * 100 + SUBMINOR. + This is not required, but it's an easy way to ensure that the version + increases for every release. + + + Every entry in xine_plugin_info has an initialization + function for the plugin class context. + This function returns a pointer to freshly allocated (typically + via malloc()) structure containing mainly function + pointers; these are the "methods" of the plugin class. + + + The "plugin class" is not what we call to do the job yet (like decoding + a video or something), it must be instantiated. One reason for having the + class is to hold any global settings that must be accessed by every + instance. Remember that xine library is multistream capable: multible + videos can be decoded at the same time, thus several instances of the + same plugin are possible. + + + If you think this is pretty much an object-oriented aproach, + then you're right. + + + A fictitious file input plugin that supports input plugin API 12 and + 13, found in xine-lib 2.13.7 would then define this plugin list: + +   #include <xine/plugin.h> +   ... +   plugin_t *init_api12(void) { +    input_plugin_t *this; +    +    this = malloc(sizeof(input_plugin_t)); +    ... +    return (plugin_t *)this; +   } +   /* same thing, with different initialization for API 13 */ +    +   const plugin_info_t xine_plugin_info[] = { +    { PLUGIN_INPUT, 12, "file", 21307, init_api12 }, +    { PLUGIN_INPUT, 13, "file", 21307, init_api13 }, +    { PLUGIN_NONE, 0, "", 0, NULL } +   } + This input plugin supports two APIs, other plugins might provide a + mixture of demuxer and decoder plugins that belong together somehow + (ie. share common code). + + + You'll find exact definitions of public functions and plugin structs + in the appropriate header files for each plugin type: + input/input_plugin.h for input plugins, + demuxers/demux.h for demuxer plugins, + xine-engine/video_decoder.h for video decoder plugins, + xine-engine/audio_decoder.h for audio decoder plugins, + xine-engine/post.h for post plugins, + xine-engine/video_out.h for video out plugins, + xine-engine/audio_out.h for audio out plugins. + Additional information will also be given in the dedicated sections below. + + + Many plugins will need some additional "private" data fields. + These should be simply added at the end of the plugin structure. + For example a demuxer plugin called "foo" with two private + fields "xine" and "count" may have a plugin structure declared in + the following way: + +   typedef struct { +    /* public fields "inherited" from demux.h */ +    demux_plugin_t demux_plugin; +    +    xine_t *xine; +    int count; +   } demux_foo_t; + + + The plugin would then access public members via the + demux_plugin field and private fields directly. + + + Summary: Plugins consist of two C-style classes, each representing a different context. + + + + The first is the so called "plugin class" context. This is a singleton context, + which means it will exist either not at all or at most once per xine context. + This plugin class context is a C-style class which is subclassing the related + class from the xine plugin headers. This contains functions, which are + independent of the actual instance of the plugin. Most prominently, it contains + a factory method to instantiate the next context. + + + + + The second context is the instance context. This is another C-style class, which + is constructed and disposed withing the plugin class context. This one does + the actual work and subclasses the related plugin struct from the xine plugin + headers. It is instantiated for every separate running instance of the plugin + + + + + + + + + What is this metronom thingy? + + Metronom serves two purposes: + + + + Generate vpts (virtual presentation time stamps) from pts (presentation time stamps) + for a/v output and synchronization. + + + + + Provide a master clock (system clock reference, scr), possibly provided + by external scr plugins (this can be used if some hardware decoder or network + server dictates the time). + + + + + + pts/vpts values are given in 1/90000 sec units. pts values in mpeg streams + may wrap (that is, return to zero or any other value without further notice), + can be missing on some frames or (for broken streams) may "dance" around + the correct values. Metronom therefore has some heuristics built-in to generate + clean vpts values which can then be used in the output layers to schedule audio/video + output. + + + The heuristics used in metronom have always been a field of research. Current metronom's + implementation tries to stick to pts values as reported from demuxers, + that is, vpts may be obtained by a simple operation of vpts = pts + vpts_offset, + where vpts_offset takes into account any wraps. Whenever pts is zero, + metronom will estimate vpts based on previous values. If a difference is found between the + estimated and calculated vpts values by above formula, it will be smoothed by using a + "drift correction". + + + + + How does xine synchronize audio and video? + + Every image frame or audio buffer leaving decoder is tagged by metronom with + a vpts information. This will tell video_out and audio_out threads when that + data should be presented. Usually there isn't a significative delay associated + with video driver, so we expect it to get on screen at the time it's + delivered for drawing. Unfortunately the same isn't true for audio: all sound + systems implement some amount of buffering (or fifo), any data being send to it + now will only get played some time in future. audio_out thread + must take this into account for making perfect A-V sync by asking the sound latency + to audio driver. + + + Some audio drivers can't tell the current delay introduced in playback. This is + especially true for most sound servers like ESD or aRts and explain why in such + cases the sync is far from perfect. + + + Another problem xine must handle is the sound card clock drift. vpts are + compared to the system clock (or even to a different clock provided by a scr plugin) + for presentation but sound card is sampling audio by it's own clocking + mechanism, so a small drift may occur. As the playback goes on this + error will accumulate possibly resulting in audio gaps or audio drops. To avoid that + annoying effect, two countermeasures are available (switchable with xine config + option audio.av_sync_method): + + + + The small sound card errors are feedbacked to metronom. The details + are given by audio_out.c comments: + +   /* By adding gap errors (difference between reported and expected +    * sound card clock) into metronom's vpts_offset we can use its +    * smoothing algorithms to correct sound card clock drifts. +    * obs: previously this error was added to xine scr. +    * +    * audio buf ---> metronom --> audio fifo --> (buf->vpts - hw_vpts) +    * (vpts_offset + error) gap +    * <---------- control --------------| +    * +    * Unfortunately audio fifo adds a large delay to our closed loop. +    * +    * These are designed to avoid updating the metronom too fast. +    * - it will only be updated 1 time per second (so it has a chance of +    * distributing the error for several frames). +    * - it will only be updated 2 times for the whole audio fifo size +    * length (so the control will wait to see the feedback effect) +    * - each update will be of gap/SYNC_GAP_RATE. +    * +    * Sound card clock correction can only provide smooth playback for +    * errors < 1% nominal rate. For bigger errors (bad streams) audio +    * buffers may be dropped or gaps filled with silence. +    */ + + + + + The audio is stretched or squeezed a slight bit by resampling, thus compensating + the drift: The next comment in audio_out.c explains: + +   /* Alternative for metronom feedback: fix sound card clock drift +    * by resampling all audio data, so that the sound card keeps in +    * sync with the system clock. This may help, if one uses a DXR3/H+ +    * decoder board. Those have their own clock (which serves as xine's +    * master clock) and can only operate at fixed frame rates (if you +    * want smooth playback). Resampling then avoids A/V sync problems, +    * gaps filled with 0-frames and jerky video playback due to different +    * clock speeds of the sound card and DXR3/H+. +    */ + + + + + + + + Overlays and OSD + + The roots of xine overlay capabilities are DVD subpictures and subtitles support + (also known as 'spu'). The DVD subtitles are encoded in a RLE (Run Length Encoding - the + most simple compressing technique) format, with a palette of colors and transparency + levels. You probably thought that subtitles were just simple text saved into DVDs, right? + Wrong, they are bitmaps. + + + In order to optimize to the most common case, xine's internal format for screen overlays + is a similar representation to the 'spu' data. This brings not only performance + benefit (since blending functions may skip large image areas due to RLE) but also + compatibility: it's possible to reencode any xine overlay to the original spu format + for displaying with mpeg hardware decoders like DXR3. + + + Displaying subtitles requires the ability to sync them to the video stream. This + is done using the same kind of pts/vpts stuff of a-v sync code. DVD subtitles, + for example, may request: show this spu at pts1 and hide it at pts2. This brings the + concept of the 'video overlay manager', that is a event-driven module for managing + overlay's showing and hiding. + + + The drawback of using internal RLE format is the difficulty in manipulating it + as graphic. To overcome that we created the 'OSD renderer', where OSD stands + for On Screen Display just like in TV sets. The osd renderer is a module + providing simple graphic primitives (lines, rectagles, draw text etc) over + a "virtual" bitmap area. Everytime we want to show that bitmap it will + be RLE encoded and sent to the overlay manager for displaying. + + + + + + + + + + overlays architecture + + + + Overlay Manager + + The overlay manager interface is available to any xine plugin. It's a bit unlikely + to be used directly, anyway here's a code snippet for enqueueing an overlay for + displaying: + +   video_overlay_event_t event; +    +   event.object.handle = this->video_overlay->get_handle(this->video_overlay,0); +    +   memset(this->event.object.overlay, 0, sizeof(*this->event.object.overlay)); +    +   /* set position and size for this overlay */ +   event.object.overlay->x = 0; +   event.object.overlay->y = 0; +   event.object.overlay->width = 100; +   event.object.overlay->height = 100; +    +   /* clipping region is mostly used by dvd menus for highlighting buttons */ +   event.object.overlay->clip_top = 0; +   event.object.overlay->clip_bottom = image_height; +   event.object.overlay->clip_left = 0; +   event.object.overlay->clip_right = image_width; +    +   /* the hard part: provide a RLE image */ +   event.object.overlay->rle = your_rle; +   event.object.overlay->data_size = your_size; +   event.object.overlay->num_rle = your_rle_count; +    +   /* palette must contain YUV values for each color index */ +   memcpy(event.object.overlay->clip_color, color, sizeof(color)); +    +   /* this table contains transparency levels for each color index. +    0 = completely transparent, 15 - completely opaque */ +   memcpy(event.object.overlay->clip_trans, trans, sizeof(trans)); +    +   /* set the event type and time for displaying */ +   event.event_type = EVENT_SHOW_SPU; +   event.vpts = 0; /* zero is a special vpts value, it means 'now' */ +   video_overlay->add_event(video_overlay, &event); + + + + OSD Renderer + + OSD is a general API for rendering stuff over playing video. It's available both + to xine plugins and to frontends. + + + The first thing you need is to allocate a OSD object for drawing from the + renderer. The code below allocates a 300x200 area. This size can't be changed + during the lifetime of a OSD object, but it's possible to place it anywhere + over the image. + + +   osd_object_t osd; +    +   osd = this->osd_renderer->new_object(osd_renderer, 300, 200); + + Now we may want to set font and color for text rendering. Although we will + refer to fonts over this document, in fact the OSD can be any kind of bitmap. Font + files are searched and loaded during initialization from + $prefix/share/xine/fonts/ and ~/.xine/fonts. + There's a sample utility to convert truetype fonts at + xine-lib/misc/xine-fontconv.c. Palette may be manipulated directly, + however most of the time it's convenient to use pre-defined text palettes. + + +   /* set sans serif 24 font */ +   osd_renderer->set_font(osd, "sans", 24); +    +   /* copy pre-defined colors for white, black border, transparent background to +    starting at the index used by the first text palette */ +   osd_renderer->set_text_palette(osd, TEXTPALETTE_WHITE_BLACK_TRANSPARENT, OSD_TEXT1); +    +   /* copy pre-defined colors for white, no border, translucid background to +    starting at the index used by the second text palette */ +   osd_renderer->set_text_palette(osd, TEXTPALETTE_WHITE_NONE_TRANSLUCID, OSD_TEXT2); + + Now render the text and show it: + +   osd_renderer->render_text(osd, 0, 0, "white text, black border", OSD_TEXT1); +   osd_renderer->render_text(osd, 0, 30, "white text, no border", OSD_TEXT2); +    +   osd_renderer->show(osd, 0); /* 0 stands for 'now' */ + + + There's a 1:1 mapping between OSD objects and overlays, therefore the + second time you send an OSD object for displaying it will actually substitute + the first image. By using set_position() function we can move overlay + over the video. + + +   for( i=0; i < 100; i+=10 ) { +    osd_renderer->set_position(osd, i, i ); +    osd_renderer->show(osd, 0); +    sleep(1); +   } +   osd_renderer->hide(osd, 0); + + For additional functions please check osd.h or the public header. + + + OSD palette notes + + The palette functions demand some additional explanation, skip this if you + just want to write text fast without worring with details! :) + + + We have a 256-entry palette, each one defining yuv and transparency levels. + Although xine fonts are bitmaps and may use any index they want, we have + defined a small convention: + + +   /* +    Palette entries as used by osd fonts: +    +    0: not used by font, always transparent +    1: font background, usually transparent, may be used to implement +    translucid boxes where the font will be printed. +    2-5: transition between background and border (usually only alpha +    value changes). +    6: font border. if the font is to be displayed without border this +    will probably be adjusted to font background or near. +    7-9: transition between border and foreground +    10: font color (foreground) +   */ + + The so called 'transitions' are used to implement font anti-aliasing. That + convention requires that any font file must use only the colors from 1 to 10. + When we use the set_text_palette() function we are just copying 11 palette + entries to the specified base index. + + + That base index is the same we pass to render_text() function to use the + text palette. With this scheme is possible to have several diferent text + colors at the same time and also draw fonts over custom background. + + +   /* obtains size the text will occupy */ +   renderer->get_text_size(osd, text, &width, &height); +    +   /* draws a box using font background color (translucid) */ +   renderer->filled_rect(osd, x1, y1, x1+width, y1+height, OSD_TEXT2 + 1); +    +   /* render text */ +   renderer->render_text(osd, x1, y1, text, OSD_TEXT2); + + + OSD text and palette FAQ + + Q: What is the format of the color palette entries? + + + A: It's the same as used by overlay blending code (YUV). + + + Q: What is the relation between a text palette and a palette + I set with xine_osd_set_palette? + + + A: xine_osd_set_palette will set the entire 256 color palette + to be used when we blend the osd image. + "text palette" is a sequence of 11 colors from palette to be + used to render text. that is, by calling osd_render_text() + with color_base=100 will render text using colors 100-110. + + + Q: Can I render text with colors in my own palette? + + + A: Sure. Just pass the color_base to osd_render_text() + + + Q: Has a text palette change effects on already drawed text? + + + A: osd_set_text_palette() will overwrite some colors on palette + with pre-defined ones. So yes, it will change the color + on already drawed text (if you do it before calling osd_show, + of course). + If you don't want to change the colors of drawed text just + use different color_base values. + + + Q: What about the shadows of osd-objects? Can I turn them off + or are they hardcoded? + + + A: osd objects have no shadows by itself, but fonts use 11 + colors to produce an anti-aliased effect. + if you set a "text palette" with entries 0-9 being transparent + and 10 being foreground you will get rid of any borders or + anti-aliasing. + + + + + + + MRLs + + This section defines a draft for a syntactic specification of MRLs as + used by xine-lib. The language of MRLs is designed to be a true subset + of the language of URIs as given in RFC2396. A type 2 grammar for the + language of MRLs is given in EBNF below. + + + Semantically, MRLs consist of two distinct parts that are evaluated by + different components of the xine architecture. The first part, + derivable from the symbol <input_source> in the given grammar, is + completely handed to the input plugins, with input plugins signaling + if they can handle the MRL. + + + The second part, derivable from <stream_setup> and delimited from the + first by a crosshatch ('#') contains parameters that modify the + initialization and playback behaviour of the stream to which the MRL + is passed. The possible parameters are mentioned in the manpage to + xine-ui. + + + The following definition should be regarded as a guideline only. + Of course any given input plugin only understands a subset of all + possible MRLs. On the other hand, invalid MRLs according to this + definition might be understood for convenience reasons. + Some user awareness is required at this point. + + + EBNF grammar for MRLs: + +   <mrl> ::= <input_source>[#<stream_setup>] +   <input_source> ::= (<absolute_mrl>|<relative_mrl>) +   <absolute_mrl> ::= <input>:(<net_path>|<abs_path>)[?<query>] +   <relative_mrl> ::= (<abs_path>|<rel_path>) +   <net_path> ::= //<authority>[<abs_path>] +   <abs_path> ::= /<path_segments> +   <rel_path> ::= <rel_segment>[<abs_path>] +   <rel_segment> ::= <rel_char>{<rel_char>} +   <rel_char> ::= (<unreserved>|<escaped>|;|@|&|=|+|$|,) +   <input> ::= <alpha>{(<alpha>|<digit>|+|-|.)} +   <authority> ::= (<server>|<reg_name>) +   <server> ::= [[<userinfo>@]<host>[:<port>]] +   <userinfo> ::= {(<unreserved>|<escaped>|;|:|&|=|+|$|,)} +   <host> ::= (<hostname>|<ipv4_address>) +   <hostname> ::= {<domainlabel>.}<toplabel>[.] +   <domainlabel> ::= (<alphanum>|<alphanum>{(<alphanum>|-)}<alphanum>) +   <toplabel> ::= (<alpha>|<alpha>{(<alphanum>|-)}<alphanum>) +   <ipv4_address> ::= <digit>{<digit>}.<digit>{<digit>}.<digit>{<digit>}.<digit>{<digit>} +   <port> ::= {<digit>} +   <reg_name> ::= <reg_char>{<reg_char>} +   <reg_char> ::= (<unreserved>|<escaped>|;|:|@|&|=|+|$|,) +   <path_segments> ::= <segment>{/<segment>} +   <segment> ::= {<path_char>}{;<param>} +   <param> ::= {<path_char>} +   <path_char> ::= (<unreserved>|<escaped>|:|@|&|=|+|$|,) +   <query> ::= {<mrl_char>} +   <stream_setup> ::= <stream_option>;{<stream_option>} +   <stream_option> ::= (<configoption>|<engine_option>|novideo|noaudio|nospu) +   <configoption> ::= <configentry>:<configvalue> +   <configentry> ::= <unreserved>{<unreserved>} +   <configvalue> ::= <conf_char>{<conf_char>} +   <engine_option> ::= <unreserved>{<unreserved>}:<stream_char>{<stream_char>} +   <stream_char> ::= (<unreserved>|<escaped>|:|@|&|=|+|$|,) +   <mrl_char> ::= (<reserved>|<unreserved>|<escaped>) +   <reserved> ::= (;|/|?|:|@|&|=|+|$|,) +   <unreserved> ::= (<alphanum>|<mark>) +   <mark> ::= (-|_|.|!|~|*|'|(|)) +   <escaped> ::= %<hex><hex> +   <hex> ::= (<digit>|A|B|C|D|E|F|a|b|c|d|e|f) +   <alphanum> ::= (<alpha>|<digit>) +   <alpha> ::= (<lowalpha>|<upalpha>) +   <lowalpha> ::= (a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z) +   <upalpha> ::= (A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z) +   <digit> ::= (0|1|2|3|4|5|6|7|8|9) + + + + -- cgit v1.2.3