tag:blogger.com,1999:blog-21346007817559056112024-03-13T18:15:47.787+01:00Truths or lies - decide yourselfburkhardhttp://www.blogger.com/profile/17023643103552829447noreply@blogger.comBlogger60125tag:blogger.com,1999:blog-2134600781755905611.post-61066400929719144992014-07-14T22:48:00.001+02:002014-07-14T22:48:44.351+02:00Introducing gavftools<b>1. Introduction</b><br />
<b> </b>
<br />
The goal when developing the <a href="http://hirntier.blogspot.de/2013/04/gavf-multimedia-container-format-for.html">gavf</a>-Format was
mainly to have a universal pipe format, which can be used to stream multimedia content from one program to another.<br />
In apps/gavftools, there is a bunch of commandline programs I wrote for making my everyday multimedia work much easier. They allow to build quite complex processing pipelines from the commandline.
<br />
The basis is the gavf multimedia format, which can contain audio, video as well as text- and graphical subtitles. A/V data can be either uncompressed or compressed in a large number of formats. In some cases, especially for uncompresed video, the video frames are passed as shared memory segments between the processes.<br />
All programs are prefixed by <code>gavf-</code>. All programs can be called with the <code>-help</code> argument to show commandline options.<br />
<br />
<b>2. Simple examples</b><br />
<br />
Read a media file and convert it to the gavf format:<br />
<br />
<code>gavf-decode -i file.avi -o file.gavf</code><br />
<br />
or:<br />
<br />
<code>gavf-decode -i file.avi > file.gavf</code><br />
<br />
Play a media file:<br />
<br />
<code>gavf-decode -i file.avi | gavf-play</code><br />
<br />
<b>3. I/O variants</b><br />
<br />
Playing a media file can happen in many ways. Instead of<br />
<br />
<code>gavf-decode -i file.avi | gavf-play</code><br />
<br />
You can use a unix-domain socket:<br />
<br />
<code>gavf-decode -i file.avi -o unixserv://socket </code><br />
<code>gavf-play -i unix://socket</code><br />
<br />
(of course the 2 commands should be called in different terminals or the first command should be put into the background). You can also use a fifo:<br />
<br />
<code>mkfifo fifo</code><br />
<code>gavf-decode -i file.avi -o fifo</code><br />
<code>gavf-play -i fifo</code><br />
<br />
Or a TCP socket:<br />
<br />
<code>gavf-decode -i file.avi -o httpserv://192.128.100.1:8888</code><br />
<code>gavf-play -i http://192.128.100.1:8888</code><br />
<br />
Naturally in the last example the decode and playback commands can run on different machines.<br />
<br />
Shared memory segments are always used if the maximum packet size is known in advance and the receiver is a process (i.e. not a file) running on the same machine.<br />
<br />
<b>4. Other commands</b><br />
<br />
Recompress the the audio stream to 320 kbps mp3. This can also be used to recompress audio and video simultaneously:<br />
<br />
<code>... | gavf-recompress -ac 'codec=c_lame{cbr_bitrate=320}' | ....</code><br />
<br />
Split audio- and video stream into separate files:<br />
<br />
<code>... | gavf-demux -oa audio.gavf -ov video.gavf</code><br />
<br />
Multiplex separate streams:<br />
<br />
<code>gavf-mux -i audio.gavf -i video.gavf | ....</code><br />
<br />
Display info about the stream (don't do anything else)<br />
<br />
<code>... | gavf-info</code><br />
<br />
Split a stream for multiple receivers (can also use more than 2 -o options):<br />
<br />
<code>... | gavf-tee -o saved_file.gavf -o "|gavf-play"</code><br />
<br />
Record a stream from your webcam and from your soundcard (replace <code>pulseaudio_device</code> with something meaningful):<br />
<br />
<code>gavf-record -vid 'plugin=i_v4l2{device=/dev/video0}' -aud 'plugin=i_pulse{dev=pulseaudio_device}' | ...</code><br />
<br />
Convert an audio-only stream to mp3. If the audio compression is mp3 already, it is written as it is, else it is encoded with 320 kbps:<br />
<br />
<code>... | gavf-encode -enc "a2v=0:ae=e_lame" -ac cbr_bitrate=320 -o file.mp3</code><br />
<br />
Flip video images vertically:<br />
<br />
<code>... | gavf-filter -vf 'f={fv_flip{flip_v=1}}' | ....</code><br />
<br />
Can also be used for adding audio filters with the -af option. Filters can also be chained.burkhardhttp://www.blogger.com/profile/17023643103552829447noreply@blogger.com0tag:blogger.com,1999:blog-2134600781755905611.post-34493537546658549982013-04-03T16:53:00.000+02:002013-04-03T16:56:52.766+02:00gavf: A multimedia container format for gmerlin<b>Introduction</b><br />
<br />
Having programmed a lot of demultiplexers in gmerlin-avdecoder I found out that there is no ideal container format. For my taste an ideal container format<br />
<ul>
<li>Is as codec agnostic as possible, i.e. doesn't require codec specific hacks in (de-)multiplexers. AVI is surprisingly good in this respect. Ogg and mov/mp4 fail miserably here.</li>
<li>Supports sample accurate timing. This means that all streams have timestamps in their native timescale. This is solved well in Ogg and mp4, while matroska and many other formats fail.</li>
<li>Is fully streamable. This means that a stream can be encoded from a live source and sent over a (non-seekable) channel like a socket. Ogg streams have this property but mov/mp4 doesn't.</li>
<li>Is as simple as possible.</li>
</ul>
Designing a multimedia format for gmerlin was mostly a matter of serializing the C-structs, which were already present in gavl, like A/V formats, compression descriptions and metadata. Furthermore I used some tricks:<br />
<ul>
<li>Use variable length integers like in matroska but extended for 64 bit</li>
<li>Introduce so called synchronization headers. They come typically before video keyframes and contain timestamps of the next packets for all elementary streams. If you seek to a sync header you have the full information about the timing when you restart decoding from that point.</li>
<li>Write timestamps relative to the last sync header. This means smaller numbers (fewer bytes) but full accuracy and 64 bit resolution. A similar approach is found in matroska files.</li>
<li>Eliminate redundant fields. E.g. a video stream with constant framerate and no B-frames doesn't need per-frame timestamps at all.</li>
<li>Split global per-stream information into a header (at the beginning of a file) and a footer (at the end of the file). For decoding the file (e.g. when streaming) the header is sufficient. The footer contains e.g. the indices for seeking. In the case of a live stream, there is no footer at all. But it can be generated trivially when the stream is saved to a file.</li>
<li>Make bitstream-level parsing of the elemtary streams unnecessary. This means that some fields, which might come handy on the demuxer level, are available in the container format. Examples are the frame type (I-, P- or B-frame) and timecodes.</li>
<li>Support arbitrary global and per-stream metadata</li>
<li>Allow to update the global metadata on-the-fly. This allows to wrap webradio streams into gavf streams without loosing the song titles.</li>
<li>Support chapters and subtitles (text based and graphical).</li>
</ul>
<b>Motivation</b><br />
<br />
Now the question is, why yet another multimedia format? Well it's true that there are way too many formats out there as every multimedia programmer knows too well. So let me make clear why I developed gavf. I wanted:<br />
<ul>
<li>to store uncompressed A/V data in all formats, which are supported by gavl. This is especially important for testing and debugging</li>
<li>to save a compressed stream (e.g. from an rtsp source) without depending on 3rd party libraries</li>
<li>to transfer A/V streams from one gmerlin program to another via a pipe or a socket.</li>
<li>to prove, that I can design a format, which is better than all the others :)</li>
</ul>
All these goals couldn't be met with any existing container format, but they are all met by gavf, so it was worth the effort.<br />
<br />
<b>Supported codecs</b><br />
<br />
As mentioned already, gavf supports compressed and uncompressed data. In the uncompressed case, the format is completely defined by the audio- or video format, the ID of the compression info is set to <code>GAVL_CODEC_ID_NONE</code> then. For audio streams, the compression can be one of the following:
<br />
<ul>
<li>alaw
</li>
<li>ulaw
</li>
<li>mp2
</li>
<li>mp3
</li>
<li>AC3
</li>
<li>AAC
</li>
<li>Vorbis
</li>
<li>Flac
</li>
<li>Opus
</li>
<li>Speex
</li>
</ul>
For video, we support:
<br />
<ul>
<li>JPEG
</li>
<li>PNG
</li>
<li>TIFF
</li>
<li>TGA
</li>
<li>MPEG-1
</li>
<li>MPEG-2
</li>
<li>MPEG-4 (a.k.a. Divx)
</li>
<li>H.264 (including AVCHD)
</li>
<li>Theora
</li>
<li>Dirac
</li>
<li>DV (several variants)
</li>
<li>VP8
</li>
</ul>
These allow to wrap a huge number of formats into gavf streams. Adding new codecs is mostly a matter of defining them in <code>gavl/compression.h</code> and adding support for them in gmerlin-avdecoder at least.<br />
<br />
<b>Application support
</b><br />
<br />
I won't promote gavf as a container format for interchanging multimedia content. In fact the current design makes it even impossible. The gavf format can change without a warning from one gavl version to another. And there are no version fields inside gavf files to ensure backward compatibility. For now, I use it exclusively for passing files between gmerlin applications of the same version.<br />
<br />
If, however, someone likes gavf so much that he or she wants it to be more wide spread, it needs some additional work. First of all we need a formal specification document. Secondly we need to add version fields to the internal data structures so one can write backwards compatible (de-)muxers. None of these will be done by me though.<br />
<br />
The current svn version of gmerlin has some support for gavf:<br />
<ul>
<li>A reference (de-)multiplexer in gavl (<code>gavl/gavf.h</code>)
</li>
<li>gavf demultiplexing support in gmerlin-avdecoder
</li>
<li>An encoder plugin for creating gavf files with gmerlin_transcoder. It supports compressing with the <a href="http://hirntier.blogspot.com/2013/04/standalone-codec-plugins-for-gmerlin.html">standalone codecs</a></li>
<li>A connector for reading and writing gavf streams via regular files, pipes and sockets. It's the basis of the <b>gavftools</b>, which will be described in another post.
</li>
</ul>
burkhardhttp://www.blogger.com/profile/17023643103552829447noreply@blogger.com0tag:blogger.com,1999:blog-2134600781755905611.post-35924397660548402102013-04-02T20:28:00.000+02:002013-04-02T20:28:07.673+02:00Standalone codec plugins for gmerlinAfter having implemented the <a href="http://hirntier.blogspot.com.br/2013/04/software-av-connectors-for-gmerlin.html">A/V connectors</a> for gmerlin, it was easy to implement standalone codec plugins, which (de-)compress an A/V stream. This means that in addition to simplified A/V processing with on-the-fly format
conversion we can also do on-the-fly (de-)compression.
There is just one plugin type (for compression and decompression of audio and video): <code>bg_codec_plugin_t</code> defined in <code>gmerlin/plugin.h</code>. In addition to the common stuff (creation, destruction, setting parameters), there are a number of functions, which are specific to codec functionality. For decompression these are:<br />
<br />
<pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code>gavl_audio_source_t * (*connect_decode_audio)(void * priv,
gavl_packet_source_t * src,
const gavl_compression_info_t * ci,
const gavl_audio_format_t * fmt,
gavl_metadata_t * m);
gavl_video_source_t * (*connect_decode_video)(void * priv,
gavl_packet_source_t * src,
const gavl_compression_info_t * ci,
const gavl_video_format_t * fmt,
gavl_metadata_t * m);
</code></pre>
<br />
The decompressor will get the compressed packets from the packet source. Additional arguments are the compression info, the format (which might be incomplete) and the metadata of the A/V stream. They return an audio- or video source, from where you can read the uncompressed frames.<br />
<br />
For opening a compressor, we need to call one of:<br />
<br />
<pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code>gavl_audio_sink_t * (*open_encode_audio)(void * priv,
gavl_compression_info_t * ci,
gavl_audio_format_t * fmt,
gavl_metadata_t * m);
gavl_video_sink_t * (*open_encode_video)(void * priv,
gavl_compression_info_t * ci,
gavl_video_format_t * fmt,
gavl_metadata_t * m);
gavl_video_sink_t * (*open_encode_overlay)(void * priv,
gavl_compression_info_t * ci,
gavl_video_format_t * fmt,
gavl_metadata_t * m);
</code></pre>
<br />
It will return the sink where we can push the A/V frames. The other arguments are the same as if we open a decoder, but in this case they will be changed by the call. After opening the compressor and before passing the first frame, we need to set a packet sink where the compressed packets will be written:<br />
<br />
<pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code>void (*set_packet_sink)(void * priv, gavl_packet_sink_t * s);
</code></pre>
<br />
The decompressors work in pull mode, the compressors work in push mode. These are the most suitable modes in typical usage scenarios.<br />
<br />
The potential delay between compressed packets and uncompressed frames is handled internally. The decompressor simply reads enough packets to it can output one uncompressed frame. The compressor outputs compressed frame as they become available. When the compressor is destroyed, it might flush it's internal state resulting in one or more compressed packets to be written. This means that at the moment you destroy a compressor, the packet sink must still be able to accept packets.<br />
<br />
There are decompressor plugins as part of gmerlin-avdecoder, which handle most formats. The gmerlin-encoders package contains compressor plugins for most formats as well.burkhardhttp://www.blogger.com/profile/17023643103552829447noreply@blogger.com0tag:blogger.com,1999:blog-2134600781755905611.post-25349577558889485832013-04-02T15:54:00.000+02:002013-04-02T16:01:50.327+02:00Software A/V connectors for gmerlinAs mentioned <a href="http://hirntier.blogspot.com/2013/02/gmerlin-architecture-changes.html">earlier</a>, I programmed generic connectors for A/V frames and compressed packets. They are much more sophisticated than the <a href="http://hirntier.blogspot.com/2008/11/gmerlin-pipelines-explained.html">old</a> API (based on callback functions), because they also do implicit format conversion and buffer management. The result is a simplified plugin API (consisting of fewer functions) and simplified applications. The stuff is implemented in gavl (include <code>gavl/connectors.h</code>), so it can be used in gmerlin as well as in gmerlin_avdecoder without introducing new library dependencies.
There are 3 types of modules:<br />
<ul>
<li>Sources work in pull mode and do format conversion. They are used by input- and recording plugins</li>
<li>Sinks work in push mode and are used by output and encoding plugins</li>
<li>Connectors connect multiple sinks to a source</li>
</ul>
<b>Example for the API usage</b><br />
Assuming you want to read audio samples from a media file and send it to a sink. When you get an audio source (e.g. from gemerlin_avdecoder with <code>bgav_get_audio_source()</code>), your application can look like:<br />
<br />
<pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code>gavl_audio_source_t * src;
gavl_audio_sink_t * sink;
gavl_audio_frame_t * f;
gavl_source_status_t st;
/* Get source */
src = bgav_get_audio_source(dec, 0);
/* Tell the source to deliver the format needed by the sink */
gavl_audio_source_set_dst(src, 0, gavl_audio_sink_get_format(sink));
/* Processing loop */
while(1)
{
/* Get a frame of internally allocated memory from the sink
* (e.g. shared or mmamp()ed memory). Return value can be NULL.
*/
f = gavl_audio_sink_get_frame(sink);
/* Read a frame from the source, if f == NULL we'll get a frame
* allocated and owned by the source itself
*/
st = gavl_audio_source_read_frame(src, &f);
if(st != GAVL_SOURCE_OK)
break;
if(gavl_audio_sink_put_frame(sink, f) != GAVL_SINK_OK)
break;
}
</code></pre>
<br />
If you want to use the <code>gavl_audio_connector_t</code>, things get even a bit simpler:<br />
<br />
<pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"><code>gavl_audio_source_t * src;
gavl_audio_sink_t * sink;
gavl_audio_connector_t * conn;
/* Get source */
src = bgav_get_audio_source(dec, 0);
/* Create connector */
conn = gavl_audio_connector_create(src);
/* Connect sink (you can connect multiple sinks) */
gavl_audio_connector_connect(conn, sink);
/* Initialize */
gavl_audio_connector_start(conn);
/* Processing loop */
while(gavl_audio_connector_process(conn))
;
</code></pre>
<br />
The gmerlin plugin API was changed to use only the sources and sinks for passing around frames. Text subtitles are transported in gavl packets, overlay subtitles are transported in video frames.<br />
<br />
In addition to the lower level gavl converters, the sources support some more format conversions. For audio frames, we do buffering such that the number of samples per frame you read from the source can be different from what the source natively delivers. For video, we support a simple framerate conversion, which works by repeating of dropping frames.<br />
<br />
The video processing API is completely analogous to the audio API described above. For compressed packets, things are slightly different because we don't do format conversion on compressed packets.<br />
<br />
A number of gmerlin modules (e.g. the player and transcoder) are already converted to the new API. In many cases, lots of redundant code could be kicked out, so the resulting code is much simpler and easier to understand.burkhardhttp://www.blogger.com/profile/17023643103552829447noreply@blogger.com0tag:blogger.com,1999:blog-2134600781755905611.post-71003820512757267452013-02-13T23:56:00.000+01:002013-02-13T23:56:09.756+01:00Gmerlin architecture changesIt was a long time ago that I wrote something about the latest gmerlin developments. The reason for that is, that most of the time I was too busy coding and too lazy for documenting stuff. For the latter you need a stable architecture and the architecture changes a bit during the development. I usually think a lot before starting coding. But at some point I need to flush my brain and fine tune things later when I have some working applications.<br /><br />The gmerlin architecture was reworked dramatically with the following goals:<br />
<ol>
<li>Implement generic source and sink connectors for transporting A/V frames and (compressed) packets inside one application. These do automatic format conversion and optimized buffer handling.</li>
<li>Change the handling of A/V streams throughout all libraries to use the new connectors. This includes gmerlin-avdecoder as well as the gmerlin plugin API.</li>
<li>Implement standalone codec plugins for on-the-fly (de-)compression of A/V streams.</li>
<li>Define (yet another) Multimedia container format. It can be used as an on-disk format but also (and more importantly) as a generic pipe format for connecting commandline applications. Think of it as a more generic version of the yuv4mpeg format. It is called <b>gavf</b>.</li>
<li>Define an interprocess transport mechanism for gavf streams through pipes or sockets. On machine local connections it can pass A/V frames through shared memory for increased efficiency.</li>
<li>Write a bunch of commandline tools for generating and processing gavf streams, which can be connected in every imaginable way on the Unix commandline. This was the ultimate goal I had in my mind :)</li>
</ol>
Not everything is finished yet. I'll document each of these subprojects in separate posts.burkhardhttp://www.blogger.com/profile/17023643103552829447noreply@blogger.com0tag:blogger.com,1999:blog-2134600781755905611.post-31922672553109551462011-10-22T21:58:00.002+02:002011-10-22T22:49:39.354+02:00On demand audio streaming with icecastThe project goal was to make the impossible happen: Turn an icecast streaming server into an audio-on-demand server.<br /><br />The background is, that I bought a NAS, which is basically a PC with Atom CPU. After erasing the firmware and installing Ubuntu Server on a 10 TB Raid 5 system, I was thinking what else I could do with the box.<br /><br />Live streaming via icecast to my Wifi-radio worked for <a href="http://hirntier.blogspot.com/2008/09/music-from-everywhere-everywhere.html">some time</a> now, but this needs a running PC with a soundcard. What I had in my mind, was different:<br /><ul><li>It should run exclusively on the NAS, no need to switch on a PC<br /></li><li>It should support an arbitrary number of playlists, each one corresponding to an icecast URL.<br /></li><li>The upstream mechanism should work <em>on demand</em> because encoding many mp3 streams in parallel overloads the Atom CPU.<br /></li><li>The current song should be shown in the display of the radio<br /></li></ul><b>Song titles</b><br />For the last requirement I added live metadata updating to the API for gmerlin broadcasting plugins. After learning, that Vorbis streams with changing song titles make my radio reboot, I wrote an MP3 broadcasting plugin (with libshout and lame). It seems that later firmware versions for the radio fix the vorbis problem, but the firmware update requires a windows software.<br /><br /><b>Commandline recorder</b><br />The recording and broadcast architecture for gmerlin was already working reliably, so I wrote a plugin, which takes a gmerlin album (=playlist), shuffles the tracks and makes them available as if it record from a soundcard. In addition, I wrote a commandline recorder, which could be started from a script. There is one script for starting a broadcast:<br /><br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>$cat start_broadcast.sh<br /><br />#!/bin/sh<br /><br />BITRATE=320<br />NAME="NAS $1"<br />STATION_DIR="/nas/Stations/lists/"<br />PASSWORD="secret"<br /><br />AUDIO_OPT='do_audio=1:plugin=i_audiofile{album='$STATION_DIR$1':shuffle=1}'<br />VIDEO_OPT="do_video=0"<br />METADATA_OPT="metadata_mode=input"<br />ENC_OPT='audio_encoder=b_lame{server=nas_ip:mount=/'$1':password='$PASSWORD':name='$NAME':cbr_bitrate='$BITRATE'}'<br /><br />gmerlin-record -aud $AUDIO_OPT -vid $VIDEO_OPT -m $METADATA_OPT -enc "$ENC_OPT" -r 2>> /dev/null >> /dev/null &<br />echo $! > $1.pid<br /><br /></code></pre><br />If you call the script with <code>start_broadcast.sh foo</code>, it will load the album <code>/nas/Stations/lists/foo</code> and send the stream to the icecast server, which will make it available under <code>nas.ip:8000/foo</code>. In addidion, the PID of the process will be written to <code>./foo.pid</code> so it can be stopped later.<br /><br />The foo broadcast can be stopped with <code>stop_broadcast.sh foo</code>, where the<br />script looks like:<br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>#!/bin/sh<br />kill -9 `cat $1.pid`<br />rm -f $1.pid<br /></code></pre><br /><b>Icecast configuration</b><br />No critical options had to be changed in the icecast configuration, except queue-size, which was doubled to 1048576 because it's better for 320 kbps streams.<br /><br /><b>Icecast stats in awk friendly format</b><br />For the on-demand meachism described below, we also need to get the<br />running channels and connected clients from the server ideally in an awk friendly<br />format. This is done by getting the server statistics in xml format and process it<br />with <code>xsltproc</code>, a small commandline tool which comes with libxml2:<br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>$cat get_stats.sh<br /><br />#!/bin/sh<br />wget --user=admin --password=secret -O - http://127.0.0.1:8000/admin/stats.xml 2> /dev/null | \<br />xsltproc stats.xsl - | cut -b 2-<br /></code></pre>If you have two channels foo (1 listener) and bar (2 listeners) it will output<br /><code><br />foo 1<br />bar 2<br /></code><br />The transformation file <code>stats.xsl</code> looks like:<br /><br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code><?xml version="1.0" encoding="UTF-8"?><br /><xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><br /><xsl:output method="text"/><br /><xsl:template match="/"><br /> <xsl:for-each select="icestats/source"><br /> <xsl:value-of select="@mount"/><br /> <xsl:text> </xsl:text><br /> <xsl:value-of select="listeners"/><br /> <xsl:text><br /></xsl:text><br /> </xsl:for-each><br /></xsl:template><br /></xsl:stylesheet><br /></code></pre><b>On demand mechanism</b><br />Now since we have commands for starting, stopping and querying channels, we can start a channel when the first listener connects and stop it after the last listener disconnected. Since icecast doesn't support on demand streaming, we must trick it into doing so. The idea is to put a second http server in front of the icecast server, which handles the connection requests, starts the channel (if necessary) and then does a http redirect to the real icecast url. The icecast server runs on port 8000, the redirection server (to which the listeners connect) runs on port 8001. The redirection server can be built simply within shell scripts using the netcat (traditional) utility. The server script is simple:<br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>$cat server.sh<br />#!/bin/sh<br /><br />cd /nas/mmedia/Stations<br /><br />while true; do<br />nc.traditional -l -p 8001 -c ./handle.sh<br />done<br /></code></pre>Whenever a TCP connection on port 8001 arrives, the following handler script is executed:<br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>$cat handle.sh<br />#!/bin/bash<br /><br /># Read request, path and protocol<br />read REQ URLPATH PROTO<br /># Read header variables<br />while true; do<br />read VAR VAL<br />if test "x$VAL" = "x"; then<br />break<br />fi<br />done<br /><br /># Reject anything but GET requests<br />if test "x$REQ" != "xGET"; then<br />echo -e "HTTP/1.1 400 Bad Request\r\n\r\n"<br />exit<br />fi<br /><br /># Remove leading "/"<br />FILE=`echo $URLPATH | cut -b 2-`<br /><br /># Close unused streams<br />./clean.sh $FILE<br /><br /># Check if we are broadcasting already<br />RESULT=`./query_station.sh $FILE`<br />if test "x$RESULT" = "x"; then<br />./start_broadcast.sh $FILE 2>> /dev/null &<br />sleep 1<br />fi<br /><br /># Send redirection header<br />URL="http://nas_ip:8000/$FILE"<br />echo -e "HTTP/1.1 307 Temporary Redirect\r\nLocation: $URL\r\n\r\n"<br /><br /></code></pre>Here we use 2 additional scripts. <code>clean.sh</code> stops all streams with zero listeners except the one, which was given as commandline argument.<br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>#!/bin/sh<br />./get_stats.sh | awk -v NAME=$1 '($1 != NAME) && ($2 == 0) { system("./stop_broadcast.sh " $1) }'<br /></code></pre><code>query_station.sh</code> lists just the number of listeners of the given station:<br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>#!/bin/sh<br />./get_stats.sh | awk -v NAME=$1 '$1 == NAME { print $2 }'<br /></code></pre><b>Energy saving mode</b><br />When we just use the radio, the NAS must be switched on manually. The PCs do that automatically with wake-on-lan. The NAS detects, when it is no longer needed and switches off automatically then. This is done by querying the TCP connections to IP addresses other than localhost. If we don't have any external connections for more than 30 minutes, we switch off. The following script can be interesting for many other applications as well. Simply start it during booting:<br /><pre style="font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; border: 1px dashed rgb(153, 153, 153); line-height: 14px; padding: 5px; overflow: auto; width: 100%;"><code>#!/bin/sh<br /># Switch off after this time<br />THRESHOLD=1800<br /># Delay between 2 checks<br />DELAY=60<br /><br />DATE_START=`date +%s`<br /><br />while :<br />do<br /> CONNECTIONS=`netstat -tn | grep tcp | grep -v " 127\." | wc -l`<br /> DATE_NOW=`date +%s`<br /><br /> if test "x$CONNECTIONS" = "x0"; then<br /> DATE_DIFF=`echo "$DATE_NOW - $DATE_START - $THRESHOLD" | bc`<br /> if test $DATE_DIFF -gt "0"; then<br /> poweroff<br /> exit<br /> fi<br /> else<br /> DATE_START=$DATE_NOW<br /> fi<br /> sleep $DELAY<br />done<br /><br /></code></pre><br /><br />Mission accomplished.burkhardhttp://www.blogger.com/profile/17023643103552829447noreply@blogger.com4tag:blogger.com,1999:blog-2134600781755905611.post-21707797505562444392010-12-09T01:31:00.003+01:002010-12-09T01:34:40.360+01:00New prereleasesLots of bugs have been fixed after the last prereleases, so here are<br />new ones:<br /><br /><a href="http://gmerlin.sourceforge.net/gmerlin-dependencies-20101209.tar.bz2">http://gmerlin.sourceforge.net/gmerlin-dependencies-20101209.tar.bz2</a><br /><a href="http://gmerlin.sourceforge.net/gmerlin-all-in-one-20101209.tar.bz2">http://gmerlin.sourceforge.net/gmerlin-all-in-one-20101209.tar.bz2</a><br /><br />The good news is, that no new features were added so the code can stabilize better.<br /><br />Please test this and report any problems.<br /><br />The final gmerlin release is expected by the end of the yearburkhardhttp://www.blogger.com/profile/17023643103552829447noreply@blogger.com0tag:blogger.com,1999:blog-2134600781755905611.post-87213238875587092402010-09-18T17:09:00.000+02:002010-09-18T17:10:51.465+02:00gmerlin prereleasesSome time has gone since the last prereleases, and a lot of bugs have been<br />fixed since then. So here is another round:<br /><br /><a href="http://gmerlin.sourceforge.net/gmerlin-dependencies-20100918.tar.bz2">http://gmerlin.sourceforge.net/gmerlin-dependencies-20100918.tar.bz2</a><br /><a href="http://gmerlin.sourceforge.net/gmerlin-all-in-one-20100918.tar.bz2">http://gmerlin.sourceforge.net/gmerlin-all-in-one-20100918.tar.bz2</a>burkhardhttp://www.blogger.com/profile/17023643103552829447noreply@blogger.com0tag:blogger.com,1999:blog-2134600781755905611.post-75281140943047327912010-08-07T02:10:00.006+02:002010-08-07T02:30:34.651+02:00Gmerlin configuration improvementsUp to now, gmerlins configuration philosophy was simple: Export all user settable parameters as possible to the frontends, no matter now important they are. There are 2 reasons for that:<br /><ul><li>As a developer, I don't like to decide which configuration options are <i>important</i>.<br /></li><li>As a user I (personally) want to have full control over all program-and plugin settings. Nothing annoys me more in other applications than features, which could easily achieved by the backend, but they are not supported in the frontend.<br /></li></ul>The downside of this approach is simple: For an average user, the gmerlin applications are way too complicated. And of course, for me it's also annoying to tweak that many parameters all the time. Now, since I reach a one-zero version, It's time to look at such usability issues.<br /><br /><b>A little look behind the GUI</b><br />A configuration dialog can contain of multiple nested sections. If you have more than one section, you see a tree structure on the right, which lets you select the <i>section</i>. A <i>section</i> contains all the configuration widgets you can see at the same time. Therefore the code must always distinguish if an action is for a section or for the whole dialog.<br /><br /><b>Factory defaults</b><br />Most configuration sections now have a button <i>Restore factory defaults</i>. It does, what the name suggests. You can use this if you think you messed something up.<br /><br /><b>Presets</b><br />Some configuration sections support presets. You can save all parameters into a file and load them again after. In some situations, presets are per <i>section</i>. In this case you see the preset menu below the parameter widgets. If the presets are global for the whole dialog window, you see the menu below the tree view. The next image shows a single-section dialog with the preset menu next to the restore button.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTYNde41bcEqc4-Epudv657R8RbRs9_hhbXVCpADorLzIj6qW8g6gJx2WcexR6OjM0JKW1JBN2037Z0eY8O5LZyhMUz9HuTFOUcIrHxX9Nvwakkkbj2lEoBDTIByeFBUY1BAZjvc2972o/s1600/cfg_deint.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 246px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTYNde41bcEqc4-Epudv657R8RbRs9_hhbXVCpADorLzIj6qW8g6gJx2WcexR6OjM0JKW1JBN2037Z0eY8O5LZyhMUz9HuTFOUcIrHxX9Nvwakkkbj2lEoBDTIByeFBUY1BAZjvc2972o/s400/cfg_deint.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5502458737946397634" /></a><br /><br />The next image shows a dialog with multiple sections. The preset menu is for the whole dialog, the restore button is for the section only.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNecU9-Jf3WRnmfDPeXi1t2zWKoBDmmwKbclpXbzw9YDSeEWYaucdwBTize0f7PRA8oua3-LEY_tsdbC4tOY5xHwuPdiTqV4pIn33r-8L5M_9uSjkF-If0G0oSRPCoGCqr9F2ugMaBrAA/s1600/cfg_cropscale.png"><img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 400px; height: 226px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgNecU9-Jf3WRnmfDPeXi1t2zWKoBDmmwKbclpXbzw9YDSeEWYaucdwBTize0f7PRA8oua3-LEY_tsdbC4tOY5xHwuPdiTqV4pIn33r-8L5M_9uSjkF-If0G0oSRPCoGCqr9F2ugMaBrAA/s400/cfg_cropscale.png" alt="" id="BLOGGER_PHOTO_ID_5502456119893114386" border="0" /></a><br /><br />The presets are designed such, that multiple applications can share them. E.g. an encoding setup configured in the transcoder can be reused in the recorder etc. Presets are available for:<br /><ul><li>All plugins (always global for the whole plugin)<br /></li><li>Whole encoding setups<br /></li><li>Filter chains<br /></li></ul>There is no reason, not to support presets for other configurations as well. Suggestions are welcome.burkhardhttp://www.blogger.com/profile/17023643103552829447noreply@blogger.com0tag:blogger.com,1999:blog-2134600781755905611.post-77594164453902483832010-08-03T20:43:00.002+02:002010-08-03T20:47:44.390+02:00Gmerlin prereleasesgmerlin prereleases can be downloaded here:<br /><br /><a href="http://gmerlin.sourceforge.net/gmerlin-dependencies-20100803.tar.bz2">http://gmerlin.sourceforge.net/gmerlin-dependencies-20100803.tar.bz2</a><br /><br /><a href="http://gmerlin.sourceforge.net/gmerlin-all-in-one-20100803.tar.bz2">http://gmerlin.sourceforge.net/gmerlin-all-in-one-20100803.tar.bz2</a><br /><br />Highlights of this development iteration:<br /><ul><li><a href="http://hirntier.blogspot.com/2010/05/processing-compressed-streams-with.html">Pass-through of compressed streams in the transcoder</a><br /></li><li>Native matroska demuxer with sample accurate seeking in webm files<br /></li><li> VP8 decoding via ffmpeg<br /></li><li><a href="http://hirntier.blogspot.com/2010/08/getting-serious-with-sample-accuracy.html">Lots of work on precise timing and sample accurate seeking</a><br /></li><li>Presets in the GUI configuration (will blog later about that)<br /></li><li>The gmerlin package reaches version one-zero<br /></li></ul>Please test this as much as possible and report any problems.burkhardhttp://www.blogger.com/profile/17023643103552829447noreply@blogger.com0tag:blogger.com,1999:blog-2134600781755905611.post-69262732155267446002010-08-01T14:30:00.008+02:002010-08-01T14:53:34.134+02:00Getting serious with sample accuracygmerlin-avdecoder has a sample accurate seek API for some time now. What was missing was a test to prove that seeking happens really with sample accuracy.<br /><br /><b>Test tool</b><br />The strictest test if a decoder library can seek with sample accuracy is to seek to a position, decode a video frame or a bunch of audio samples. Compare these with the frame/samples you get if you decode the file from the beginning. Of course, the timestamps must also be identical. A tool, which does this, is in <code>tests/seektest.c</code>. I noticed, that video streams easily pass this test, usually even if no sample accurate access was requested. That's probably because I thought, that video streams are more difficult. So I put more brainload into them. Therefore I'll concentrate on audio streams in this post.<br /><br /><b>Audio codec details</b><br />When seeking in video streams, you have keyframes, which tell you where decoding of a stream can be resumed after a seek. It's sometimes difficult to implement this, but at least you always know what to do.<br /><br />The naive approach for audio streams is to assume, that all blocks (e.g. 1152 samples for mp3) can be decoded independently. Unfortunately, reality is a bit more cruel:<br /><br /><i>Bit reservoir</i><br />This is a mechanism, which allows to make pseudo VBR in a CBR stream. If a frame can be encoded with fewer bits than allocated for the frame, it can leave the remaining bits to a subsequent (probably more complex) frame. The downside of this trick is, that after a seek, the next frame might need bits from previous frames to be fully decoded.<br /><br /><i>Oberlapping transform</i><br />Most audio compression techniques work in the frequency-domain, so between the audio signal and the compression stage, there is some kind of fft-like transform.<br /><br />Now, for reasons beyond this post, <a href="http://en.wikipedia.org/wiki/Overlap-add_method">overlapping</a> transforms are used by some codecs. This means, that for decoding the first samples of a compressed block, you need the last samples of the previous block. The image below shows one channel of an AAC stream for the case that the overlapping was ignored when seeking. You see that the beginning of the frame is not reconstructed properly, because the previous frame is missing.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiSXqhUQ3Wv2ZUk4uVAEK2dMr_01SupLfexPRfC3nF5RtGDTENp-v7sZUJ5cUg3_1SjUWBD82gSOAMB65_W0MiOXQOo1ryDqmeswEjvGX8JMdzqFz3ylv4FSAPGdekhvybsKNKX-e_kKx8/s1600/overlap.png"><img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 400px; height: 306px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiSXqhUQ3Wv2ZUk4uVAEK2dMr_01SupLfexPRfC3nF5RtGDTENp-v7sZUJ5cUg3_1SjUWBD82gSOAMB65_W0MiOXQOo1ryDqmeswEjvGX8JMdzqFz3ylv4FSAPGdekhvybsKNKX-e_kKx8/s400/overlap.png" alt="" id="BLOGGER_PHOTO_ID_5500418885995491250" border="0" /></a><br /><br />Both the bit reservoir and the overlapping can be boiled down to a single number, which tells how many sample <i>before</i> the actual seek point the decoder must restart decoding. This number is set by the codec during initialization, and it's used when we seek with sample accuracy.<br /><br /><i>Mysterious liba52 behavior</i><br />Even if sample accuracy was achieved, the AC3 streams (which are on DVDs or in AVCHD files) don't achieve bit exactness. The image below shows, that there is no time shift between the signals (which means that gmerlin-avdecoder seeks correctly), but the values are not exactly the same.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-yZEfdB7lV-KOsrSgicEdMiwJ1GDl02hvSKMfVYKegoc3CTBADgZcKb7btSN2Wh7xM-P_TjmsnXk66TB1XQ_VrC7F6GXE4oRibY8a801j3N9b63ePTVHmcz99O2wxSstxiwyqocHV3fQ/s1600/memory.png"><img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 400px; height: 306px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-yZEfdB7lV-KOsrSgicEdMiwJ1GDl02hvSKMfVYKegoc3CTBADgZcKb7btSN2Wh7xM-P_TjmsnXk66TB1XQ_VrC7F6GXE4oRibY8a801j3N9b63ePTVHmcz99O2wxSstxiwyqocHV3fQ/s400/memory.png" alt="" id="BLOGGER_PHOTO_ID_5500419544268777170" border="0" /></a><br /><br />First I blamed the AC3 dynamic range control for this behavior. Dynamic range compressors always have some kind memory across several frames. But even after disabling DRC, the difference was still there. I would really be curious if that's a principal property of AC3 being non-deterministic or if it's a liba52 bug.<br /><br /><b>Conclusions</b><br />The table below lists all audio codecs, which were taken into consideration. They represent a huge percentage of all files found in the wild. The next important codecs are the uncompressed ones, but these are always sample accurate.<br /><table border="1"><br /><tr><br /><td>Compression </td><td>Library </td><td>Overlap</td><td>Bit reservoir </td><td>Bit exact</td></tr><tr><br /><td>MPEG-1, layer II </td><td>libmad </td><td>- </td><td>- </td><td>+<br /></td></tr><tr><br /><td>MPEG-1, layer III</td><td>libmad </td><td>+ </td><td>+ </td><td>+<br /></td></tr><tr><br /><td>AAC </td><td>faad2 </td><td>+ </td><td>? (assumed -) </td><td>+<br /></td></tr><tr><br /><td>AC3 </td><td>liba52 </td><td>+ </td><td>? (assumed -) </td><td>- (see image above)<br /></td></tr><tr><br /><td>Vorbis </td><td>libvorbis </td><td>+ </td><td>- </td><td>+<br /></td></tr></table><br />Obtaining the information summarized here was a very painful process with web researches and experiments. The documentation of the decoder libraries regarding sample accurate and bit exact seeking is <i>extremely</i> sparse if not non-existing.burkhardhttp://www.blogger.com/profile/17023643103552829447noreply@blogger.com1tag:blogger.com,1999:blog-2134600781755905611.post-21807010648614688632010-05-01T02:19:00.004+02:002010-05-01T02:41:54.867+02:00Processing compressed streams with gmerlinAs I already mentioned, a main goal of this development cycle is to read compressed streams on the input side and write compressed streams on the encoding side. It's a bit of work, but it's definitely worth it because it offers enormous possibilities:<br /><ul><li>Lossless transmultiplexing from one container to another<br /></li><li>Adding/removing streams of a file without recompressing the other streams.<br /></li><li>Lossless concatenation of compressed files<br /></li><li>Changing metadata of files (i.e. mp3/vorbis tagging)<br /></li><li>Quicktime has some codecs, which correspond to image formats (png, jpeg, tiff, tga). Supporting compressed frames can convert single images to quicktime movies and back<br /></li><li>In some cases broken files can be fixed as well<br /></li></ul><b>General approach</b><br />To limit the possibilities of creating broken files, we are a bit strict about the<br />codecs we support for compressed I/O. This means, that with the new feature you cannot automatically transfer all compressed streams. For compressed I/O the following conditions<br />must be met:<br /><ul><li>The precise codec must be known to gavl. While for decoding it never matters if we have MPEG-1 or MPEG-2 video (libmpeg2 decodes both), for compressed I/O it must be known.<br /></li><li>For some codecs, we need other parameters like the bitrate or if the stream contains B-frames or field pictures.<br /></li><li>Each audio packet must consist of an independently decompressable frame and we must know, how many uncompressed samples are contained.<br /></li><li>For each video packet, we must know the pts, how long the frame will be displayed and if it's a keyframe.<br /></li></ul><b>Compression support in gavl</b><br />For transferring compressed packets, we need 2 data structures:<br /><ul><li>An info structure, which describes the compression format (i.e. the codec). The actual codec is an enum (similar to ffmpegs CodecID), but other parameters can be required as well (see above).<br /></li><li>A structure for a data packet.<br /></li></ul>Both of these are in gavl in a new header file <a href="http://gmerlin.cvs.sourceforge.net/viewvc/gmerlin/gavl/include/gavl/compression.h?view=markup"><code>gavl/compression.h</code></a>. Gavl itself <b>never</b> messes around with the contents of compressed packets, if just provides some housekeeping functions for packets and compression definitions. The definitions were moved here, because it's the only common dependency of gmerlin and gmerlin-avdecoder and I didn't want to define that twice.<br /><br /><b>gmerlin-avdecoder</b><br />There are 2 new functions for getting the compression format of A/V streams:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>int bgav_get_audio_compression_info(bgav_t * bgav, int stream,<br /> gavl_compression_info_t * info)<br /><br />int bgav_get_video_compression_info(bgav_t * bgav, int stream,<br /> gavl_compression_info_t * info)<br /></code></pre>They can be called after the track was selected with <code>bgav_select_track()</code>. If the demuxer doesn't meet the above goals for a stream it's tried with a <a href="http://hirntier.blogspot.com/2009/02/elementary-stream-parsing.html">parser</a>. If there is no parser for this stream, compressed output fails and the functions return 0.<br /><br />If you decided to read compressed packets from a stream, pass <code>BGAV_STREAM_READRAW</code> to <code>bgav_set_audio_stream()</code> or <code>bgav_set_video_stream()</code>. Then you can read compressed packets with:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>int bgav_read_audio_packet(bgav_t * bgav, int stream, gavl_packet_t * p);<br /><br />int bgav_read_video_packet(bgav_t * bgav, int stream, gavl_packet_t * p);<br /></code></pre>There is a small commandline tool <code>bgavdemux</code>, which writes the compressed packets to raw files, but only if the compression supports a raw format. This is e.g. not the case for vorbis or theora.<br /><br /><b>libgmerlin</b><br />In the gmerlin library, the new feature shows up mainly in the <a href="http://gmerlin.cvs.sourceforge.net/viewvc/gmerlin/gmerlin/include/gmerlin/plugin.h?view=markup">plugin API</a>. The input plugin (<code>bg_input_plugin_t</code>) got 4 new functions, which have the identical meaning as their counterparts in gmerlin-avdecoder:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>int (*get_audio_compression_info)(void * priv, int stream,<br /> gavl_compression_info_t * info);<br /><br />int (*get_video_compression_info)(void * priv, int stream,<br /> gavl_compression_info_t * info);<br /><br />int (*read_audio_packet)(void * priv, int stream, gavl_packet_t * p);<br /><br />int (*read_video_packet)(void * priv, int stream, gavl_packet_t * p);<br /><br /></code></pre>On the encoding side, there are 6 new functions, which are used for querying if compressed writing is possible, adding compressed A/V tracks and writing compressed A/V packets:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>int (*writes_compressed_audio)(void * priv,<br /> const gavl_audio_format_t * format,<br /> const gavl_compression_info_t * info);<br /><br />int (*writes_compressed_video)(void * priv,<br /> const gavl_video_format_t * format,<br /> const gavl_compression_info_t * info);<br /><br />int (*add_audio_stream_compressed)(void * priv, const char * language,<br /> const gavl_audio_format_t * format,<br /> const gavl_compression_info_t * info);<br /><br />int (*add_video_stream_compressed)(void * priv,<br /> const gavl_video_format_t * format,<br /> const gavl_compression_info_t * info);<br /><br />int (*write_audio_packet)(void * data, gavl_packet_t * packet, int stream);<br /><br />int (*write_video_packet)(void * data, gavl_packet_t * packet, int stream);<br /></code></pre><b>gmerlin-transcoder</b><br />In the gmerlin transcoder you have a configuration for each A/V stream:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjf_njdQee2psiq97_xa40viImtoCwAAhJRFXUJNFW2pr5p4YzA8W_gc_fWBGbG9bSEHYWYY6k26JKWGDI7mB5Q0slGhKkJUmA5wIzV-G9UwQp-Vpx0gEz2Qb3sCjdksCo2Fi0iBZ8MEZE/s1600/transcode_compressed.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 301px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjf_njdQee2psiq97_xa40viImtoCwAAhJRFXUJNFW2pr5p4YzA8W_gc_fWBGbG9bSEHYWYY6k26JKWGDI7mB5Q0slGhKkJUmA5wIzV-G9UwQp-Vpx0gEz2Qb3sCjdksCo2Fi0iBZ8MEZE/s400/transcode_compressed.png" alt="" id="BLOGGER_PHOTO_ID_5466093076024953506" border="0" /></a>The options for the stream can be "transcode", "copy (if possible)" or "forget". Copying of a stream is possible if the following conditions are met:<br /><ul><li>The source can deliver compressed packets<br /></li><li>The encoder can write compressed packets of that format<br /></li><li>No subtitles are blended onto video images<br /></li></ul>All filters are however completely ignored. You can configure any filters you want, but when you choose to copy the stream, none of them will be applied.<br /><br />If a stream cannot be copied, it will be transcoded.<br /><br /><b>libquicktime</b><br />Another major project was support in libquicktime. It's a bit nasty because libquicktime codecs do tasks, which should actually be done by the (de)multiplexer. In practice this means that compressed streams have to be enabled for each codec <b>and</b> container separately. The public API is in <a href="http://libquicktime.cvs.sourceforge.net/viewvc/libquicktime/libquicktime/include/quicktime/compression.h?view=markup">compression.h</a>. It was modeled after the functions in libgmerlin, but the definition of the compression (<code>lqt_compression_info_t</code>) is slightly different because inside libquicktime we can't use gavl.<br /><br />I made a small tool <code>lqtremux</code>. It can either be called with a single file as an argument, in which case all A/V streams are exported to separate quicktime files. If you pass more than one file on the commandline, the last file is considered the output file and all tracks of all other files are multiplexed into the output file. Note that lqtremux is a pretty dumb application, which was written mainly as a demonstration and testbed for the new functionality. In particular you cannot copy some tracks while transcoding others. For more sophisticated tasks use gmerlin-transcoder or write your own tool.<br /><br /><b>Status and TODO</b><br />Most major codecs and containers work, although not all of them are heavily tested. Therefore I cannot guarantee, that files written that way will be compatible with all other decoders. Future work will be testing, fixing and supporting more codecs in more containers. Of course any help (like bugreports or compatibility testing on windows or OSX) is highly appreciated.<br /><br />With this feature my A/V pipelines are ready for a 1.x version now.burkhardhttp://www.blogger.com/profile/17023643103552829447noreply@blogger.com0tag:blogger.com,1999:blog-2134600781755905611.post-18992460408423689602010-03-20T15:59:00.009+01:002010-03-21T00:52:27.889+01:00libtheora vs. libschrödinger vs. x264Doing a comparison of lossy codecs was always on my TODO list. That's mostly because of the codec-related noise I read (or skip) on mailing lists and propaganda for the royality free codecs with semi-technical arguments but without actual numbers. A while ago I made some quick and dirty PSNR tests, where x264 turned out to be the clear winner. But recently we saw new releases of libtheora and libschrödinger with improved encoding quality, so maybe now is the time to investigate things a bit deeper.<br /><br /><b>Specification vs implementation</b><br />With non-trivial compression techniques (and all techniques I tried are non-trivial) you must make a difference between a <i>specification</i> and an <i>implementation</i>. The <i>specification</i> defines how the compressed bitstream looks like, and suggests how the data can be compressed. I.e. it specifies if motion vectors can be stored with subpixel precision or if B-frames are possible. The <i>implementation</i> does the actual work of compressing the data. It has a large degree of freedom e.g. it lets you choose between several motion estimation methods or techniques for quantization or rate control. If you fully read and understood all specifications, you can make a rough estimation, which specification allows more powerful compression. But if you want numbers, you can only compare <i>implementations</i>.<br /><br />This implies, that statements like <i>"Dirac is better than H.264"</i> (or vice versa) are inherently idiotic.<br /><br /><b>Candidates</b><br /><ul><li>libschroedinger-1.0.9<br /></li><li>libtheora-1.1.0<br /></li><li>x264 git version from 2010-03-19<br /></li></ul><b>Rules of the game</b><br />If compression algorithms are completely different, it's not easy to find comparable codec parameters. Some codecs are very good for VBR encoding but suck when forced to CBR. Some codecs are optimized for low-bitrate, others are work better at higher bitrates. Therefore I decided for very simple test rules:<br /><ul><li>All codec settings are set to their defaults found in the sourcecode of the libraries. This leaves the decision of good parameters to the developers of the libraries. I upgraded the codec parameters in libquicktime and the gmerlin encoding plugins for the newest library versions.<br /></li><li>The only parameter, which is changed, corresponds to the global quality of the compression (all libraries have such a parameter). Multiple files are encoded with different quality settings.<br /></li><li>From the encoded files the average bitrate is calculated and the <a href="http://hirntier.blogspot.com/2010/01/video-quality-characterization.html">quality</a> (PSNR and MSSIM) is plotted as a function of the average bitrate.<br /></li></ul><b>Footage</b><br />Some lossless sequences in y4m format can be downloaded from the <a href="http://media.xiph.org/video/derf/">xiph</a> site. I wanted a file, which has fast global motion as well as slower changing parts. Also the uncompressed size shouldn't be too large to keep the transcoding- and analysis time at a reasonable level. Therefore I decided to use the <a href="http://media.xiph.org/video/derf/y4m/foreman_cif.y4m">foreman</a>. Of course for a better estimation you would need much more and longer sequences. Feel free to repeat the experiment with other files and tell about the results.<br /><br /><b>Analysis tool</b><br />I wrote a small tool <code>gmerlin_vanalyze</code>, which is called with the original and encoded files as only arguments. It will then output something like:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>0 33385 47.137417 0.992292<br />1 17713 45.936294 0.989990<br />2 17693 45.998659 0.990233<br />3 17361 46.008802 0.990297<br />4 19253 46.144632 0.990582<br />5 19005 46.179699 0.990648<br /><br />....<br /><br />295 24454 45.174282 0.993100<br />296 23841 44.653152 0.992318<br />297 20966 43.848303 0.991013<br />298 13941 41.996157 0.987494<br />299 11682 41.852630 0.987321<br /># Average values<br /># birate PSNR SSIM<br /># 4941941.26 46.075177 0.991434<br /></code></pre><br />Each line consists of:<br /><ul><li>Frame number<br /></li><li>Compressed size of this frame in bytes<br /></li><li>Luminance PSNR in dB of this frame<br /></li><li>Mean SSIM of this frame<br /></li></ul>The summary at the end consists of the total video bitrate (bits/second) as well as PSNR and SSIM averaged over all frames.<br /><br />You can get this tool if you upgrade gavl, gmerlin and gmerlin-avdecoder from CVS. It makes use of a brand new feature (extracting compressed frames), which is needed for the video bitrate calculation (i.e. without the overhead from the container).<br /><br /><b>Results</b><br />See below for the PSNR and MSSIM results for the 3 libraries.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ6lVPenT-5Dl6_2GUY2xnAfaEsJ_KMLftvRcb4Ufys_CQ6Wmz2DE9jhtc23k9jkn1mW8vCdKxIsz9rr1i-RrINoqqnIDHYImBJkdWjQ-V94eVKsiDFnthMe4sVjLzK6N9GnL2uR0TPjU/s1600-h/psnr.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 299px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJ6lVPenT-5Dl6_2GUY2xnAfaEsJ_KMLftvRcb4Ufys_CQ6Wmz2DE9jhtc23k9jkn1mW8vCdKxIsz9rr1i-RrINoqqnIDHYImBJkdWjQ-V94eVKsiDFnthMe4sVjLzK6N9GnL2uR0TPjU/s400/psnr.png" alt="" id="BLOGGER_PHOTO_ID_5450736026867457026" border="0" /></a><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh60at99OIrDc0gQZUMpDb8RDUd_NafTqqzWP2Xre4zpn2KvslmzTr8XyFeNd_tQW4TLYGmGh_GCGppBl2JGXl9ssEth1ZfZ15vCvGP4mX2OiPOFNBONpTrayIwHV8o_vB9v9ms_YJWt0s/s1600-h/ssim.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 302px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh60at99OIrDc0gQZUMpDb8RDUd_NafTqqzWP2Xre4zpn2KvslmzTr8XyFeNd_tQW4TLYGmGh_GCGppBl2JGXl9ssEth1ZfZ15vCvGP4mX2OiPOFNBONpTrayIwHV8o_vB9v9ms_YJWt0s/s400/ssim.png" alt="" id="BLOGGER_PHOTO_ID_5450736546142362530" border="0" /></a><br /><br /><b>Conclusion</b><br />The quality vs. bitrate differences are surprisingly small. While x264 still wins, the royality free versions lag behind by just 2-3 dB of PSNR. Also surprising is that libtheora and libschroedinger are so close together given the fact, that Dirac has e.g. B-frames, while theora has just I- and P-frames. Depending on your point of view, this is good news for libtheora or bad news for libschroedinger<br /><br />Another question is of course, if this comparison is completely fair. A further project could now be to take the codecs and tweak single parameters to check, how the quality can be improved. Also one might add other criteria like encoding-/decoding speed as well. Making tests with different types of footage would also give more insight.<br /><br />To summarize that, I don't state that these numbers are the final wisdom. But at least they are numbers, and neither propaganda nor marketing.burkhardhttp://www.blogger.com/profile/17023643103552829447noreply@blogger.com1tag:blogger.com,1999:blog-2134600781755905611.post-81749758828013651702010-02-06T14:54:00.003+01:002010-02-06T15:17:46.254+01:00AVCHD timecode updateAfter <a href="http://hirntier.blogspot.com/2009/08/avchd-timecodes-revealed.html">this</a> post I got some infos from a <a href="http://metadatamadness.blogspot.com/">cat</a>, which helped me to understand the AVCHD metadata format much better. This post summarizes, what I currently know.<br /><br />AVCHD metadata are stored in an SEI message of type 5 (user data unregistered). These messages start with a GUID (indicating the type of the data). The rest is not specified in the H.264 spec. For AVCHD metadata the data structure is as follows:<br /><br />1. The 16 byte GUID, which consists of the bytes<br /><br /><code>0x17 0xee 0x8c 0x60 0xf8 0x4d 0x11 0xd9 0x8c 0xd6 0x08 0x00 0x20 0x0c 0x9a 0x66</code><br /><br />2. 4 bytes<br /><br /><code>0x4d 0x44 0x50 0x4d</code><br /><br />which are "MDPM" in ASCII.<br /><br />3. One byte, which specifies the number of tags to follow<br /><br />4. Each tag begins with one byte specifying the tag type followed by 4 bytes of data.<br /><br />The date and time are stored in tags <code>0x18</code> and <code>0x19</code>.<br /><br />Tag <code>0x18</code> starts with an unknown byte. I saw values between <code>0x02</code> and <code>0xff</code> in various files. It seems however that it has a constant value for all frames in a file. The 3 remaining bytes are the year and the month in BCD coding (<code>0x20 0x09 0x08</code> means August 2009).<br /><br />The 4 bytes in tag <code>0x19</code> are the day, hour, minute and second (also BCD coded).<br /><br />There are more informations stored in this SEI message, check <a href="http://owl.phy.queensu.ca/%7Ephil/exiftool/TagNames/M2TS.html#SEI">here</a> for a list.<br /><br />If you want to make further research on this, you can download gmerlin-avdecoder from CVS, open the file <code>lib/parse_h264.c</code> and uncomment the following line (at the very beginning):<br /><br /><code>// #define DUMP_AVCHD_SEI</code><br /><br />Then you can use <code>bgavdump</code> on your files. It will decode the first 10 frames from the file. If you want to decode e.g. 100 frames, use<br /><br /><code>bgavdump -nf 100 your_file.mts</code>burkhardhttp://www.blogger.com/profile/17023643103552829447noreply@blogger.com21tag:blogger.com,1999:blog-2134600781755905611.post-8700703040354679512010-01-27T23:24:00.014+01:002010-01-28T00:49:03.096+01:00Video quality characterization techniquesWhen developing video processing algorithms and tuning them for quality, one needs proper measurement facilities, otherwise one will end up doing voodoo. This post introduces two prominent methods for calculating the differences of two images (the "original" and the "reproduced" one) and get a value, which allows to estimate, how well the images coincide.<br /><br /><b>PSNR</b><br />The most prominent method is the <a href="http://en.wikipedia.org/wiki/PSNR">PSNR</a> (peaked signal-to-noise ratio). It is based on the idea, that the reproduced image consists of the original plus a "noise signal". The noise level can be characterized by the <a href="http://en.wikipedia.org/wiki/Signal-to-noise_ratio">signal-to-noise ratio</a> and is usually given in dB. Values below 0 dB mean that the noise power is larger than the signal. For identical images (zero noise), the PSNR is infinite.<br /><br />Advantage is, that it's a well established method and the calculation is extremely simple (see <a href="http://en.wikipedia.org/wiki/PSNR">here</a> for the formula). Disadvantage is, that it is a purely mathematical calculation of the noise power, while the human psychovisual system is completely ignored.<br /><br />Thus, one can easily to make 2 images, which have different types of compression artifacts (i.e. from different codecs) and have the similar PSNR compared to the original. But one looks much better than the other. Therefore, current opinion among specialists is, that PSNR can be used or optimizing <b>one</b> codec, while it fails for comparing <b>different</b> codecs. Unfortunately, many codec comparisons in the internet still use PSNR.<br /><br /><b>SSIM</b><br />SSIM (structural similarity) was fist suggested by Zhou Wang et.al. in the paper "Image Quality Assessment: From Error Visibility to Structural Similarity" (IEEE Transactions on image processing, Vol. 13, No. 4, April 2004, <a href="http://www.cns.nyu.edu/pub/eero/wang03-reprint.pdf">PDF</a>).<br /><br />The paper is very well written and I recommend anyone, who is interested, to read it. In short: The structural similarity is composed of 3 values:<br /><ul><li>Luminance comparison<br /></li><li>Contrast comparison<br /></li><li>Structure comparison<br /></li></ul>All these components are normalized such that they are 1.0 for identical images. The SSIM index is the product of the 3 components (optionally raised by an exponent). Obviously the product will be normalized as well.<br /><br />A difference to PSNR is, that the SSIM index for a pixel is calculated by taking the surrounding pixels into account. It calculates some characteristic numbers known from statistics: The mean value, the standard deviation and correlation coefficient.<br /><br />One problem with the SSIM is, that the algorithm has some free parameters, which are slightly different in each implementation. Therefore you should be careful when comparing your results with numbers coming from a different routine. I took the parameters from the original paper, i.e. K1 = 0.01, K2 = 0.03 and an 11x11 Gaussian window with a standard deviation of 1.5 pixels.<br /><br /><b>Implementations</b><br />Both methods are available in gavl (SSIM only in CVS for now), but their APIs are slightly different. To calculate the PSNR, use:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>void gavl_video_frame_psnr(double * psnr,<br /> const gavl_video_frame_t * src1,<br /> const gavl_video_frame_t * src2,<br /> const gavl_video_format_t * format);<br /><br /></code></pre>The <code>src1</code>, <code>src2</code> and <code>format</code> arguments are obvious. The result (already in dB) is returned in <code>psnr</code> for each component. The order is RGB(A), Y'CbCr(A) or Gray(A) depending on the pixelformat. PSNR can be calculated for all pixelformats, but usually one will use a Y'CbCr format and take only the value for the Y' component. In all my tests the PSNR values for chrominance were much higher, so the luminance PSNR is the most pessimistic (i.e. most honest) value.<br /><br />For SSIM you can use:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>int gavl_video_frame_ssim(const gavl_video_frame_t * src1,<br /> const gavl_video_frame_t * src2,<br /> gavl_video_frame_t * dst,<br /> const gavl_video_format_t * format);<br /></code></pre>The arguments <code>src1</code>, <code>src2</code> and <code>format</code> are the same as for PSNR. The pixelformat however <b>must</b> be <code>GAVL_GRAY_FLOAT</code>, implying that only the luminance is taken into account. This decision was made after the experiences with PSNR. The SSIM indices for each pixel is then returned in <code>dst</code>, which must be created with the same format. The MSSIM (mean SSIM) for the whole image can then be obtained by averaging the SSIM values over all pixels. The function returns 1 if the SSIM could be calculated or 0 if the pixelformat was not <code>GAVL_GRAY_FLOAT</code> or the image is smaller than the 11x11 window.<br /><br />It never matters which image is passed in <code>src1</code> and which in <code>src2</code> because both algorithms are symmetric.<br /><br /><b>Example</b><br />Below you see 11 Lena images compressed with libjpeg at quality levels from 0 to 100 along with their PSNR, SSIM and file size:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7ZqvdrDDifeEgI1SgaVNGURdq1uS64kda11cpdFVeMk0fqGD27UUS-SMvrm2B_HyEqUDxRFxqfBB0hKtnLlVh82_miI6eTcoKXRc1c1qiiekwyCVwyZls-A-7uMUNcB3IdpvJWi4j5-E/s1600-h/lena_000.jpg"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 256px; height: 256px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7ZqvdrDDifeEgI1SgaVNGURdq1uS64kda11cpdFVeMk0fqGD27UUS-SMvrm2B_HyEqUDxRFxqfBB0hKtnLlVh82_miI6eTcoKXRc1c1qiiekwyCVwyZls-A-7uMUNcB3IdpvJWi4j5-E/s400/lena_000.jpg" alt="" id="BLOGGER_PHOTO_ID_5431554562898463986" border="0" /></a><br /><div align="center">Quality: 0, PSNR: 23.54 dB, SSIM: 0.6464, Size: 2819 bytes</div><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh372luZLnL-cHkGbFLrBvxDfnHsYPCGfBW7sRIvrj_ZdLOfWSIIf8KUp9WcThPq7DpURH1C_MmuT3QAVbJf7j_ajLx4x7KAMOPZg1WobVGi64-wsJpTXPs1fb8fn4EMwSYQN36mJvbRs8/s1600-h/lena_010.jpg"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 256px; height: 256px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh372luZLnL-cHkGbFLrBvxDfnHsYPCGfBW7sRIvrj_ZdLOfWSIIf8KUp9WcThPq7DpURH1C_MmuT3QAVbJf7j_ajLx4x7KAMOPZg1WobVGi64-wsJpTXPs1fb8fn4EMwSYQN36mJvbRs8/s400/lena_010.jpg" alt="" id="BLOGGER_PHOTO_ID_5431555406884247554" border="0" /></a><br /><div align="center">Quality: 10, PSNR: 29.84 dB, SSIM: 0.8473, Size: 4305 bytes</div><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOvET3Mi-l3aMjj5Frr0_fL_6TzdYRHCKWkTC76plpOhSRyiQoYWnOL-BRdWNLbogeT4gMd6HtuFRsYpHnplPvXlSb5Rtkmi_MyWH8aAvLaMczL4U033qGJcqjBd5UmAaqgcpwuzrc31Q/s1600-h/lena_020.jpg"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 256px; height: 256px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjOvET3Mi-l3aMjj5Frr0_fL_6TzdYRHCKWkTC76plpOhSRyiQoYWnOL-BRdWNLbogeT4gMd6HtuFRsYpHnplPvXlSb5Rtkmi_MyWH8aAvLaMczL4U033qGJcqjBd5UmAaqgcpwuzrc31Q/s400/lena_020.jpg" alt="" id="BLOGGER_PHOTO_ID_5431555549189096226" border="0" /></a><br /><div align="center">Quality: 20, PSNR: 32.74 dB, SSIM: 0.9084, Size: 5890 bytes</div><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhiHXofycSxdcGMxoS2N0m526kxhaClaRtk9RSGanaHRVF15nFPbdP2Vc7e33prVUxHIPyyfMwhCpjDIRQ4E8LsVbrgCAIpqzpq1eqgs31CTxLS091VdIkV6wVqZ0rurtGAO6j_yzg4Xfo/s1600-h/lena_030.jpg"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 256px; height: 256px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhiHXofycSxdcGMxoS2N0m526kxhaClaRtk9RSGanaHRVF15nFPbdP2Vc7e33prVUxHIPyyfMwhCpjDIRQ4E8LsVbrgCAIpqzpq1eqgs31CTxLS091VdIkV6wVqZ0rurtGAO6j_yzg4Xfo/s400/lena_030.jpg" alt="" id="BLOGGER_PHOTO_ID_5431555686830503106" border="0" /></a><br /><div align="center">Quality: 30, PSNR: 34.38 dB, SSIM: 0.9331, Size: 7376 bytes</div><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXan5yUd7EkmnfWq6ePygY9WWF6QypmJhn1OH6etSDTlCuSMFbm1lsVoF5Pbs36xF5n4RBlEvhEItwxBEVATk78is1pqrCcOOoDb4uvQ3sLmsrBEfKPoVpyo8eoSzZ2YY-9kn1vvfW8Xs/s1600-h/lena_040.jpg"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 256px; height: 256px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXan5yUd7EkmnfWq6ePygY9WWF6QypmJhn1OH6etSDTlCuSMFbm1lsVoF5Pbs36xF5n4RBlEvhEItwxBEVATk78is1pqrCcOOoDb4uvQ3sLmsrBEfKPoVpyo8eoSzZ2YY-9kn1vvfW8Xs/s400/lena_040.jpg" alt="" id="BLOGGER_PHOTO_ID_5431555807195077938" border="0" /></a><br /><div align="center">Quality: 40, PSNR: 35.44 dB, SSIM: 0.9460, Size: 8590 bytes</div><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjd1eB_c2lirrEoencsGs652JQ9yx83lATu3JXW2gaRLEp1FdOBGLUzYfhYZDqyBfGKW-WpzO5RCjdqvMb6d2sIYvvID74iRM8z9-N73sVr5XCWRWgMzbwUKfukixtxXjy8u4Yno8bz3NU/s1600-h/lena_050.jpg"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 256px; height: 256px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjd1eB_c2lirrEoencsGs652JQ9yx83lATu3JXW2gaRLEp1FdOBGLUzYfhYZDqyBfGKW-WpzO5RCjdqvMb6d2sIYvvID74iRM8z9-N73sVr5XCWRWgMzbwUKfukixtxXjy8u4Yno8bz3NU/s400/lena_050.jpg" alt="" id="BLOGGER_PHOTO_ID_5431555914217470306" border="0" /></a><br /><div align="center">Quality: 50, PSNR: 36.31 dB, SSIM: 0.9549, Size: 9777 bytes</div><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWlnMQvprZRP5Y09ZXSAutbzHxY6AHBqGNHOV0_GHfU2kRiPtfC67JaXbPHdx9sWn_GvuMruOxNcyAaLl-2eKg3JA-dtzoBxBjjQvkZbkT2Mt4AvvofWgXSN6HMvbwUutfatCYMnkuxe0/s1600-h/lena_060.jpg"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 256px; height: 256px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWlnMQvprZRP5Y09ZXSAutbzHxY6AHBqGNHOV0_GHfU2kRiPtfC67JaXbPHdx9sWn_GvuMruOxNcyAaLl-2eKg3JA-dtzoBxBjjQvkZbkT2Mt4AvvofWgXSN6HMvbwUutfatCYMnkuxe0/s400/lena_060.jpg" alt="" id="BLOGGER_PHOTO_ID_5431556070645981458" border="0" /></a><br /><div align="center">Quality: 60, PSNR: 37.16 dB, SSIM: 0.9612, Size: 11101 bytes</div><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPBX6GXP0VOCBVhPef-PF-UsY3t3vjG0qI-CcfHvDo71T05SqRYlCB-Lmdm2C91H6m1lCnIiZiZInz6He9eW6gHo4O0B8PxzhCvcjfZh6QG46i2KLXGgEL-p9pOkfUtdclTkPiKMvAM90/s1600-h/lena_070.jpg"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 256px; height: 256px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPBX6GXP0VOCBVhPef-PF-UsY3t3vjG0qI-CcfHvDo71T05SqRYlCB-Lmdm2C91H6m1lCnIiZiZInz6He9eW6gHo4O0B8PxzhCvcjfZh6QG46i2KLXGgEL-p9pOkfUtdclTkPiKMvAM90/s400/lena_070.jpg" alt="" id="BLOGGER_PHOTO_ID_5431556190202190882" border="0" /></a><br /><div align="center">Quality: 70, PSNR: 38.34 dB, SSIM: 0.9688, Size: 13034 bytes</div><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgStTRAcf8vPvUYqV4dhrOonuLGTZnqGujK7d36MM7o5cKtsKJ4lOBshmgusLa8E4Sx5GP5mTrNqxg2mf2MKa82dcWBoWha728rrGubYjcHqHjJogqYaxk2foXHmmYu00_7jqR-3h8NltE/s1600-h/lena_080.jpg"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 256px; height: 256px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgStTRAcf8vPvUYqV4dhrOonuLGTZnqGujK7d36MM7o5cKtsKJ4lOBshmgusLa8E4Sx5GP5mTrNqxg2mf2MKa82dcWBoWha728rrGubYjcHqHjJogqYaxk2foXHmmYu00_7jqR-3h8NltE/s400/lena_080.jpg" alt="" id="BLOGGER_PHOTO_ID_5431556351649863186" border="0" /></a><br /><div align="center">Quality: 80, PSNR: 40.00 dB, SSIM: 0.9768, Size: 16410 bytes</div><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgflqFT2wQs0-6pc_Mwm11p_DsN4_AVRID3qU91G6MPgLVEmOoVgbcXsF-9cfDqHwc76LqvZoJb9BQ15Ae-b1Z7OCad3fUBLrhQRHzspfns-T2ndJ-9e3BxO76YKaO4iFW_273DfCcwCsQ/s1600-h/lena_090.jpg"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 256px; height: 256px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgflqFT2wQs0-6pc_Mwm11p_DsN4_AVRID3qU91G6MPgLVEmOoVgbcXsF-9cfDqHwc76LqvZoJb9BQ15Ae-b1Z7OCad3fUBLrhQRHzspfns-T2ndJ-9e3BxO76YKaO4iFW_273DfCcwCsQ/s400/lena_090.jpg" alt="" id="BLOGGER_PHOTO_ID_5431556465781950114" border="0" /></a><br /><div align="center">Quality: 90, PSNR: 43.06 dB, SSIM: 0.9863, Size: 24308 bytes</div><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhm5HKrBJM6nic3Ijn4vAXc2Csk0bEyztmnv28dq_VQGgVie6ji_Ube1-lmfI_LA-LBPvMYfWK-JhCuElO0WD7GEnuOSz5UNenry2kLnoLZ9B2rOWitgUa9r40C01LSa4AGLMLcLtEhglw/s1600-h/lena_100.jpg"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 256px; height: 256px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhm5HKrBJM6nic3Ijn4vAXc2Csk0bEyztmnv28dq_VQGgVie6ji_Ube1-lmfI_LA-LBPvMYfWK-JhCuElO0WD7GEnuOSz5UNenry2kLnoLZ9B2rOWitgUa9r40C01LSa4AGLMLcLtEhglw/s400/lena_100.jpg" alt="" id="BLOGGER_PHOTO_ID_5431556593668407218" border="0" /></a><br /><div align="center">Quality: 100, PSNR: 58.44 dB, SSIM: 0.9993, Size: 94169 bytes</div><br /><br />With these numbers I made a plot, which shows the PSNR and SSIM as a function of the JPEG quality:<br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjyziKKLOenV1Ba59BumHzuoEwXd6ksi7dLcUiK7ArwV9-CV49bhAdFDsA0RVkv8ndgJOBE9suymqe4mB_8bTbO0-exS0prT3tMVKMJ_RhMMTL4JK5En6RU2oumHbQjWMbHGTnsT7NJq2I/s1600-h/quality.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 306px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjyziKKLOenV1Ba59BumHzuoEwXd6ksi7dLcUiK7ArwV9-CV49bhAdFDsA0RVkv8ndgJOBE9suymqe4mB_8bTbO0-exS0prT3tMVKMJ_RhMMTL4JK5En6RU2oumHbQjWMbHGTnsT7NJq2I/s400/quality.png" alt="" id="BLOGGER_PHOTO_ID_5431563198013332002" border="0" /></a>The JPEGs have the most visible differences for qualities between 0 and 40. In this range the SSIM curve has the largest gradient. Above 40 (where the visual quality doesn't change much), the SSIM becomes more or less linear and reaches almost 1 for the best quality.<br /><br />The PSNR curve is a bit misleading. It has the steepest gradient for the highest quality. This is understandable because PSNR would become infinite for the lossless (perfect quality) case. It has however not much to do with the subjective impression. PSNR however is better for fine-tuning codecs at very high quality levels. That's because the PSNR values will change more strongly, while SSIM will always be almost one.<br /><br />Now I have the proper tools to make a comparison of different video codecs.burkhardhttp://www.blogger.com/profile/17023643103552829447noreply@blogger.com0tag:blogger.com,1999:blog-2134600781755905611.post-13983755327288355572010-01-24T02:53:00.004+01:002010-01-24T03:14:02.911+01:00Disabling the X11 screensaver from a clientOne of the most annoying experiences when watching videos with friends on cold winter evenings is, when the screensaver starts. Media players therefore need a way to switch that off by one or several means.<br /><br />The bad news is, that there is no <i>official</i> method for such a trivial task, which works on <b>all</b> installations. In addition, there is the energy saving mode, which has nothing to do with the screensaver, and must thus be disabled separately.<br /><br /><b>The Xlib method</b><br />You get the screensaver status with <code>XGetScreenSaver()</code>, disable it with <code>XSetScreenSaver()</code> and restore it after video playback. Advantage is, that this method is core X11. Disadvatage is, that it never works.<br /><br /><b>Old gnome method</b><br />Older gnome versions had a way to ping the screensaver by executing the command:<br /><br /><code>gnome-screensaver-command --poke > /dev/null 2> /dev/null</code><br /><br />Actually pinging the screensaver (which resets the idle timer) is a better method, because it restores the screensaver even if the player got killed (or crashed). The bad news is, that starting with some never gnome version (don't know exactly which), this stopped working. To make things worse, the command is still available and even gives zero return code, it's just a noop.<br /><br /><b>KDE</b><br />I never owned a Linux installation with KDE. But with a little help from a friends, I found a method. My implementation however is so ugly, that I won't show it here :)<br /><br /><b>The holy grail: Fake key events</b><br />After the old gnome variant stopped working for me, I finally found the XTest extension. It was developed to test XServers. I abuse it to send fake key events, which are handled indentically to real keystrokes. They will reset the idle counters of all screensaver variants, and will also disable the energy saving mode. <br /><br />Also it's a ping approach (with the advantage described above). But it works with an X11 protocol request instead of forking a subprocess, so the overhead will be much smaller. The documentation for the XTest extension is from 1992, so I expect it to be present on all installations, which are sufficiently new for video playback.<br /><br />Here is how I implemented it:<br /><br />1. Include <code><X11/extensions/XTest.h></code>, link with <code>-lXtst</code>.<br /><br />2. Test for presence of the XTest extension with <code>XTestQueryExtension()</code><br /><br />3. Get the keycode of the left shift key with <code>XKeysymToKeycode()</code><br /><br />4. Each 40 seconds, I press the key with<br /><br /><code>XTestFakeKeyEvent(dpy, keycode, True, CurrentTime);</code><br /><br />5. One video frame later, I release the key with<br /><br /><code>XTestFakeKeyEvent(dpy, keycode, False, CurrentTime);</code><br /><br />The one frame delay was done to make sure, that the press and release events will arrive with different timestamps. I don't want to know, what happens if press- and release-events for a key have identical timestamps.<br /><br />This method will hopefully work forever, no matter what crazy ideas the desktop developers get in the future. Also, this is one more reason <i>not</i> to use any GUI toolkit for video playback. If you use Xlib and it's extensions, you have full access to all available features of X. When using a toolkit, you have just the features the toolkit developers think you deserve.burkhardhttp://www.blogger.com/profile/17023643103552829447noreply@blogger.com2tag:blogger.com,1999:blog-2134600781755905611.post-77008980869201209392010-01-19T00:54:00.002+01:002010-01-19T01:00:37.220+01:00Gmerlin release on the horizonNew gmerlin prereleases are here:<br /><br /><a href="http://gmerlin.sourceforge.net/gmerlin-dependencies-20100119.tar.bz2">http://gmerlin.sourceforge.net/gmerlin-dependencies-20100119.tar.bz2</a><br /><br /><a href="http://gmerlin.sourceforge.net/gmerlin-all-in-one-20100119.tar.bz2">http://gmerlin.sourceforge.net/gmerlin-all-in-one-20100119.tar.bz2</a><br /><br />Changes since the last public release are:<br /><ul><li>Great <a href="http://hirntier.blogspot.com/2009/10/major-gmerlin-player-upgrade.html">player</a> simplification. Most changes are internal, but the user should notice much faster seeking. Also the GUI player now updates the video window while the seek-slider is moved.<br /></li><li>Simplification of the plugin configuration: In many places, where we had an extra dialog for configuring plugins, it was merged with the rest of the configuration.<br /></li><li>A new <a href="http://hirntier.blogspot.com/2009/12/introducing-gmerlin-recorder.html">recorder application</a> which records audio (with OSS, Pulseaudio, Alsa, Jack and ESound) and video (with V4L1, V4L2 or the new <a href="http://hirntier.blogspot.com/2009/11/x11-grabbing-howto.html">X11 grabber</a>). Output can be written into files or <a href="http://hirntier.blogspot.com/2009/12/flash-free-live-web-video-solution.html">broadcasted</a>.<br /></li><li>A new encoding frontend (used by the recorder and transcoder), which allows more consistant and unified configuration of encoding setups.<br /></li><li>The video window of the GUI player has now a gmerlin icon and is grouped together with the other windows.<br /></li><li>Like always: Tons of fixes, optimizations and smaller cleanups in all packages. Got another 18% speedup when building AVCHD indexes for example.<br /></li></ul>This will be the last gmerlin release of the 0.3.X series. There are just 2 major features left, which keep me from releasing 1.0.0:<br /><ul><li>Support for <b>compressed A/V frames</b> in the architecture. This should allow lossless transmultiplexing with the transcoder.<br /></li><li>Support for configuration presets: This will make using gmerlin applications much easier. The presets can be shared among application, i.e. once you found a good encoding preset, you can use it both in the transcoder and the recorder.<br /></li></ul><br /><br />Please test it as much as you can and sent problem reports to the gmerlin-general list.burkhardhttp://www.blogger.com/profile/17023643103552829447noreply@blogger.com0tag:blogger.com,1999:blog-2134600781755905611.post-48013581589309051102009-12-14T23:43:00.007+01:002009-12-15T00:34:10.778+01:00Flash-free live web video solutionSome time ago, we all knew that video on the web was equivalent to the proprietary Flash technology. Also I used to say, that there might be political or psychological reasons for using Ogg/Theora, but never technical ones.<br /><br />Well, the conditions have changed recently so it's time to bring an update on this.<br /><br /><b>HTML 5 video tag</b><br />The HTML 5 draft supports a <code><video></code> tag for embedding a video into a webpage as a native html element (e.g. without a plugin). Earlier versions of the draft even recommended that browsers should support Ogg/Theora as a format for the video. The Ogg/Theora recommendation was then removed and a lot of discussion was started around this. <a href="http://en.wikipedia.org/wiki/Use_of_Ogg_formats_in_HTML5">This</a> wikipedia article summarizes the issue. Nevertheless, there are a number of browsers supporting OggTheora video out of the box, among these is Firefox-3.5.<br /><br /><b>Cortado plugin</b><br />In a different development, the <a href="http://www.theora.org/cortado">Cortado</a> java applet for theora playback was written. It is GPL licensed and you can just <a href="http://downloads.xiph.org/releases/cortado/cortado_latest.jar">download</a> it and put it into your webspace.<br /><br />Now the cool thing about the <code><video></code> tag is, that browsers, which don't know about it, will display the contents between <code><video></code> and <code></video></code>, so you can include the applet code there. Researching a bit about the best way to do this, I read that the (often recommended) <code><applet></code> mechanism is not valid html. A better solution is <a href="http://hsivonen.iki.fi/test/moz/video-fallback-validation/object-type.html">here</a>.<br /><br /><b>Webmasters side</b><br />Now if you have the live-stream at <code>http://192.168.2.2:8000/stream.ogg</code> your html page will look like:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code><html><br /><head><br /><meta http-equiv="content-type" content="text/html; charset=ISO-8859-1"><br /></head><br /><body><br /><h1>Test video stream</h1><br /><video tabindex="0"<br />src="http://192.168.2.2:8000/stream.ogg"<br />controls="controls"<br />alt="Test video stream"<br />title="Test video stream"<br />width="320"<br />height="240"<br />autoplay="true"<br />loop="nolooping"><br /><object type="application/x-java-applet"<br /> width="320" height="240"><br /><param name="archive" value="cortado.jar"><br /><param name="code" value="com.fluendo.player.Cortado.class"><br /><param name="url" value="http://192.168.2.2:8000/stream.ogg"><br /><param name="autoplay" value="true"><br /><a href="http://192.168.2.2:8000/stream.ogg">Test video stream</a><br /></object><br /></video><br /></body><br /></html><br /></code></pre><br />Note that for live-streams the "autoplay" option should be given. If not, firefox will try to load the first image (automatically anyway) to show it in the video widget. Then it will stop downloading the live-stream until you click start. Pretty obvious that this will mess up live-streaming.<br /><br /><br /><b>Server side</b><br />For live streaming I just installed the <a href="http://www.icecast.org/">icecast</a> server, which came with my ubuntu. I just changed the passwords in <code>/etc/icecast/icecast.xml</code>, enabled the server in <code>/etc/default/icecast2</code> and started it with <code>/etc/init.d/icecast2 start</code>.<br /><br /><b>Upstream side</b><br />There are lots of programs for streaming to icecast servers, most of them use <a href="http://icecast.org/download.php">libshout</a>. I decided to create a new family of plugins for gmerlin: Broadcasting plugins. They have the identical API as encoder plugins (used by the transcoder or the recorder). The only difference is, that they don't produce regular files and must be realtime capable.<br /><br />Using libshout is extremely simple:<br /><ul><li>Create a shout instance with <code>shout_new()</code><br /></li><li>Set parameters with the <code>shout_set_*()</code> functions<br /></li><li>Call <code>shout_open()</code> to actually open the connection to the icecast server<br /></li><li>Write a valid Ogg/Theora stream with <code>shout_send()</code>. Actually I took my already existing Ogg/Theora encoder plugin and replaced all <code>fwrite()</code> calls by <code>shout_send()</code>.<br /></li></ul>See below for the libshout configuration of the gmerlin Ogg broadcaster.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-oSsrKaR1H0BldwacpcGhK9w-FrYqL1Hy9nl8UwlDLbQqTQIUlZHfWO75utEEDVW4Vv_rZn3XPXK7Ffc2EWxGZhlJ7kK3AMn4AAOKagHAeq4pONl3tdxjK6N0AxxYx4SwRTbrFJM4wOU/s1600-h/libshout_config.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 367px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-oSsrKaR1H0BldwacpcGhK9w-FrYqL1Hy9nl8UwlDLbQqTQIUlZHfWO75utEEDVW4Vv_rZn3XPXK7Ffc2EWxGZhlJ7kK3AMn4AAOKagHAeq4pONl3tdxjK6N0AxxYx4SwRTbrFJM4wOU/s400/libshout_config.png" alt="" id="BLOGGER_PHOTO_ID_5415229582000656754" border="0" /></a><br />Of course, some minor details were left out in my overview, read the libshout documentation for them. As upstream client, I use my new <a href="http://hirntier.blogspot.com/2009/12/introducing-gmerlin-recorder.html">recorder application</a>.<br /><br /><b>The result</b><br />See below a screenshot from firefox while it plays back a live stream:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1NIU-42eJVeF9NNkY-TxmXQWLhrXRimutqzejeylPfJQ8INIt8T-ViYxH_K7SqLRI42PH1TlMELzZK6XifIjlgURLpoW8GaiINUaX0W45E0hw5arZwdtoY8h-e9rk6hkfZf0w5Ak0sys/s1600-h/ff_shot.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 342px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1NIU-42eJVeF9NNkY-TxmXQWLhrXRimutqzejeylPfJQ8INIt8T-ViYxH_K7SqLRI42PH1TlMELzZK6XifIjlgURLpoW8GaiINUaX0W45E0hw5arZwdtoY8h-e9rk6hkfZf0w5Ak0sys/s400/ff_shot.png" alt="" id="BLOGGER_PHOTO_ID_5415235773265902946" border="0" /></a><br /><b>Open Issues</b><br />The live-video experiment went extremely smooth. I discovered however some minor issues, which could be optimized away:<br /><ul><li>Firefox doesn't recognize live-streams (i.e. streams with infinite duration) properly. It displays a seek-slider which always sticks at the very end. Detecting a http stream as live can easily be done by checking the <code>Content-Length</code> field of the http response header.<br /></li><li>The theora encoder (1.1.1 in my case) might be faster than the 1.0.0 series, but it's still way too slow. Live encoding of a 320x240 stream is possible on my machine but 640x480 isn't.<br /></li><li>The cortado plugin has a loudspeaker icon, but no volume control (or it just doesn't work with my Java installation)<br /></li></ul><b>Other news</b><br /><ul><li>With the video switched off I can send audio streams to my wlan radio. This makes my older <a href="http://hirntier.blogspot.com/2008/09/music-from-everywhere-everywhere.html">solution</a> (based on ices2) obsolete.<br /></li><li>The whole thing of course works with prerecorded files as well. In this case, you can just put the files into your webspace and your normal webserver will deliver them. No icecast needed.<br /></li></ul>burkhardhttp://www.blogger.com/profile/17023643103552829447noreply@blogger.com9tag:blogger.com,1999:blog-2134600781755905611.post-52639831950335290192009-12-06T18:08:00.004+01:002009-12-09T10:27:23.565+01:00Introducing Gmerlin recorderGmerlin-recorder is a small application, which records audio and video from hardware devices. It was written as a more generic application, which should eventually replace camelot. See below for a screenshot:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPcseFMzBon4Wuw-ShVlUr9nUYONsX5wsAuVD5dMJKFsLZbxbnaQZz9Mb66YwqN5Nhwnic6YGVwYXlAIIEMrnccj2uFdDpJCoY2uQtnfw56FYUTSs86iWs5TmtBg5DaMtimg75n1vJ2lg/s1600-h/recorder_shot.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 398px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPcseFMzBon4Wuw-ShVlUr9nUYONsX5wsAuVD5dMJKFsLZbxbnaQZz9Mb66YwqN5Nhwnic6YGVwYXlAIIEMrnccj2uFdDpJCoY2uQtnfw56FYUTSs86iWs5TmtBg5DaMtimg75n1vJ2lg/s400/recorder_shot.png" alt="" id="BLOGGER_PHOTO_ID_5412171791125358674" border="0" /></a>As sources, we support:<br /><ul><li>Audio devices via OSS, Alsa, Esound, Pulseaudio and Jack<br /></li><li>Webcams via V4L and V4L2<br /></li><li><a href="http://hirntier.blogspot.com/2009/11/x11-grabbing-howto.html">X11 grabbing</a><br /></li></ul>For monitoring you see the video image in the recorder window. For the audio you have a vumeter. With recorded streams you can do lots of stuff:<br /><ul><li>Filter the streams using gmerlin A/V filters<br /></li><li>Write the streams to files using gmerlin encoder plugins<br /></li><li>Send the encoded stream to an <a href="http://www.icecast.org">icecast</a> server (will be described in detail in another blog post)<br /></li><li>Save snapshots to images either manually or automatically<br /></li></ul>With all these features combined in the right way, you can use gmerlin-recorder for a large number of applications:<br /><ul><li>Make vlogs with your webcam and a microphone<br /></li><li>Digitize analog music<br /></li><li>Make a video tutorial for your application<br /></li><li>Start your broadcasting station<br /></li><li>Send music from your stereo to your WLAN radio<br /></li><li>Make stop-motion movies<br /></li><li>...<br /></li></ul>Until the recorder can fully replace camelot, we need the following features, which are not there yet:<br /><ul><li>Fullscreen video display<br /></li><li>Forwarding of the video stream via vloopback<br /></li></ul>burkhardhttp://www.blogger.com/profile/17023643103552829447noreply@blogger.com1tag:blogger.com,1999:blog-2134600781755905611.post-45541481668671515982009-11-15T14:17:00.004+01:002009-11-15T14:51:24.309+01:00X11 grabbing howtoOne gmerlin plugin I always wanted to program was an X11 grabber. It continuously grabs either the entire root window, or a user defined rectangular area of it. It is realized as a gmerlin video recorder plugin, which means that it behaves pretty much like a webcam.<br /><br />Some random notes follow.<br /><br /><b>Transparent grab window</b><br />The rectangular area is defined by a <i>grab window</i>, which consists <i>only</i> of a frame (drawn by the window manager). The window itself must be completely transparent such that mouse clicks are sent to the window below. This is realized using the X11 Shape extension:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>XShapeCombineRectangles(dpy, win,<br /> ShapeBounding,<br /> 0, 0,<br /> (XRectangle *)0,<br /> 0,<br /> ShapeSet,<br /> YXBanded);<br /></code></pre>The grab window can be moved and resized to define the grabbing area. This is more convenient than entering coordinates manually. After a resize, the plugin must be reopened because the image size of a gmerlin video stream cannot change within the processing loop.<br /><br /><b>Make the window sticky and always on top</b><br />Depending on the configuration the grab window can always be on top of the other windows. There is also a <i>sticky</i> option making the window appear on all desktops. This is done with the collowing code, which must be called each time <i>before</i> the window is mapped:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>if(flags & (WIN_ONTOP|WIN_STICKY))<br />{<br />Atom wm_states[2];<br />int num_props = 0;<br />Atom wm_state = XInternAtom(dpy, "_NET_WM_STATE", False);<br />if(flags & WIN_ONTOP)<br /> {<br /> wm_states[num_props++] =<br /> XInternAtom(dpy, "_NET_WM_STATE_ABOVE", False);<br /> }<br />if(flags & WIN_STICKY)<br /> {<br /> wm_states[num_props++] =<br /> XInternAtom(dpy, "_NET_WM_STATE_STICKY", False);<br /> }<br /><br />XChangeProperty(dpy, win, wm_state, XA_ATOM, 32,<br /> PropModeReplace,<br /> (unsigned char *)wm_states, num_props);<br />}<br /></code></pre><br /><b>Grabbing methods</b><br />The grabbing itself is done on the root window only. This will grab all other windows inside the grab area. The easiest method is <code>XGetImage</code>, but that allocates a new image with each call. <code>malloc()/free()</code> cycles within the processing loop should be avoided whenever possible. <code>XGetSubImage()</code> allows to pass an allocated image. Much better of course is <code>XShmGetImage()</code>. It was roughly 3 times faster than <code>XGetSubImage()</code> in my tests.<br /><br /><b>Coordinate correction</b><br />If parts of the grabbing rectangle are outside the root window, you'll get a <code>BadMatch</code> error (usually exiting the program), no matter which function you use for grabbing. You must handle this case and correct the coordinates to stay within the root window.<br /><br /><b>Mouse cursor</b><br />Grabbing works for everything displayed on the screen (including XVideo overlays) except the mouse cursor. It must be obtained and drawn "manually" onto the grabbed image. Coordinates are read with <code>XQueryPointer()</code>. The cursor image can be obtained if the XFixes extension is available. First we request cursor change events for the whole screen with<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>XFixesSelectCursorInput(dpy, root,<br /> XFixesDisplayCursorNotifyMask);<br /></code></pre>If the cursor changed <i>and</i> is within the grabbing rectangle we get the image with<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>im = XFixesGetCursorImage(dpy);<br /></code></pre>The resulting cursor image is then converted to a <code>gavl_overlay_t</code> and blended onto the grabbed image with a <code>gavl_overlay_blend_context_t</code>.<br /><br /><b>Not done (yet)</b><br />Other grabbing programs deliver images only when something has changed (resulting in a variable framerate stream). This can be achieved with the XDamage extension. Since the XDamage extension is (like many other X11 extensions) poorly documented, I didn't bother to implement this yet.<br /><br />One alternative is to use gmerlins decimate video filter, which compares the images in memory. The result will be the same, but CPU usage will be slightly increased.burkhardhttp://www.blogger.com/profile/17023643103552829447noreply@blogger.com0tag:blogger.com,1999:blog-2134600781755905611.post-4776166698537254712009-10-31T16:27:00.003+01:002009-10-31T16:35:15.347+01:00gavl color channelsWhen programming something completely unrelated, I stumbled across a missing feature in gavl: Extract single color channels from a video frame into a grayscale frame. The inverse operation is to insert a color channel from a grayscale frame into a video frame (overwriting the contents of that channel). This allows you to assemble an image from separate color planes.<br /><br />Both were implemented with a minimalistic API (1 enum and 3 functions), which works for all 35 pixelformats. First of all we have an enum for the channel definitions:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>typedef enum<br />{<br />GAVL_CCH_RED, // Red<br />GAVL_CCH_GREEN, // Green<br />GAVL_CCH_BLUE, // Blue<br />GAVL_CCH_Y, // Luminance (also grayscale)<br />GAVL_CCH_CB, // Chrominance blue (aka U)<br />GAVL_CCH_CR, // Chrominance red (aka V)<br />GAVL_CCH_ALPHA, // Transparency (or, to be more precise, opacity)<br />} gavl_color_channel_t;<br /><br /></code></pre>For getting the exact grayscale format for one color channel you first call:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>int gavl_get_color_channel_format(const gavl_video_format_t * frame_format,<br /> gavl_video_format_t * channel_format,<br /> gavl_color_channel_t ch);<br /></code></pre>It returns 1 on success or 0 if the format doesn't have the requested channel. After you have the channel format, extracting and inserting is done with:<br /><pre style="border: 1px dashed rgb(153, 153, 153); padding: 5px; overflow: auto; font-family: Andale Mono,Lucida Console,Monaco,fixed,monospace; color: rgb(0, 0, 0); background-color: rgb(238, 238, 238); font-size: 12px; line-height: 14px; width: 100%;"><code>int gavl_video_frame_extract_channel(const gavl_video_format_t * format,<br /> gavl_color_channel_t ch,<br /> const gavl_video_frame_t * src,<br /> gavl_video_frame_t * dst);<br /><br />int gavl_video_frame_insert_channel(const gavl_video_format_t * format,<br /> gavl_color_channel_t ch,<br /> const gavl_video_frame_t * src,<br /> gavl_video_frame_t * dst);<br /></code></pre>In the gmerlin tree, there are test programs <code>exctractchannel</code> and <code>insertchannel</code> which test the functions for all possible combinations of pixelformats and channels. They are in the gmerlin tree and not in gavl because we load and save the test images with gmerlin plugins.burkhardhttp://www.blogger.com/profile/17023643103552829447noreply@blogger.com0tag:blogger.com,1999:blog-2134600781755905611.post-44375734468573388732009-10-24T01:54:00.006+02:002009-10-24T02:01:06.040+02:00Major gmerlin player upgradeWhile working on several optimization tasks of the player engine, I found out that the player architecture sucked. So I made a major upgrade (well, a downgrade actually since lots of code was kicked out). Let me elaborate what exactly was changed.<br /><br />Below you see a block schematics of the player engine as it was before (subtitle handling is omitted for simplicity):<br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgf3nBMBv9akwDGGg1_sWWQuRG_2_4Q_AdOADdY7bl2SFUhmOnQkx7pBaA_kEC68YWStdMsYxJTIBRxsbORyAPVvo7Z-Xj0JXigoyFI24YN_0rsdsBFsjPc_m7GXDIUtZhzVlsCP987Z-A/s1600-h/player_old.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 163px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgf3nBMBv9akwDGGg1_sWWQuRG_2_4Q_AdOADdY7bl2SFUhmOnQkx7pBaA_kEC68YWStdMsYxJTIBRxsbORyAPVvo7Z-Xj0JXigoyFI24YN_0rsdsBFsjPc_m7GXDIUtZhzVlsCP987Z-A/s400/player_old.png" alt="" id="BLOGGER_PHOTO_ID_5395948279387204450" border="0" /></a>The audio- and video frames were read by the input thread from the file, pulled through the filter chains (see <a href="http://hirntier.blogspot.com/2008/11/gmerlin-pipelines-explained.html">here</a>) and pushed into the fifos.<br /><br />The output threads for audio and video pulled the frames from the fifos and sent them to the soundcard or the display window.<br /><br />The idea behind the separate input thread was that if CPU load is high and decoding a frame takes longer than it should, the output threads can still continue with the frames buffered in the fifos. It turned out that this was the <i>only</i> advantage of this approach, and it only worked if the <i>average</i> decoding time was still less than realtime.<br /><br />The major disadvantage is, that if you have fifos with frames <i>pushed</i> at the input and <i>pulled</i> at the output, the system becomes very prone to deadlocks. If fact, the code for the fifos became bloated and messy over the time.<br /><br />While programming a nice new feature (updating the video display while the seek slider is moved), the playback was messed up after seeking and I quickly blamed the fifos for this. The resulted was a big cleanup, the result is shown below:<br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUOjljID2lSsi-j8PireVJUiJyA52mjv5dElCC_ps9SKsDldZPTzfIzMttokQv4OjkvPWbOg7NH7pdI_sEepfjwQGh5w5y7ficrrsQqwiOxeRMLK0adH2bHU8lLXk25-T-uwCm12eBJSM/s1600-h/player_new.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 384px; height: 198px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUOjljID2lSsi-j8PireVJUiJyA52mjv5dElCC_ps9SKsDldZPTzfIzMttokQv4OjkvPWbOg7NH7pdI_sEepfjwQGh5w5y7ficrrsQqwiOxeRMLK0adH2bHU8lLXk25-T-uwCm12eBJSM/s400/player_new.png" alt="" id="BLOGGER_PHOTO_ID_5395948698394368594" border="0" /></a>You see, that the input thread and fifos are completely removed. Instead, the input plugin is protected by a simple mutex and the output threads do the decoding and processing themselves. The advantages are obvious:<br /><ul><li>Much less memory usage (one video frame instead of up to 8 frames in the fifo)<br /></li><li>Deadlock conditions are much less likely (if not impossible)<br /></li><li>Much simpler design, bugs are easier to fix<br /></li></ul>The only disadvantage is that if a file cannot be decoded in realtime, audio and video run out of sync. In the old design the input thread took care that the decoding of the streams didn't run too much out of sync. For these cases, I need to implement frame skipping. This can be done in several steps:<br /><ul><li>If the stream has B-frames, skip them<br /></li><li>If the stream has P-frames, skip them (display only I-frames)<br /></li><li>Display only every nth I-frame with increasing n<br /></li></ul>Frame skipping is the next major thing to do. But with the new architecture it will be much simpler to implement than with the old one.burkhardhttp://www.blogger.com/profile/17023643103552829447noreply@blogger.com0tag:blogger.com,1999:blog-2134600781755905611.post-65219685846597630032009-09-25T00:04:00.003+02:002009-09-25T00:09:25.978+02:00Frame tablesWhen thinking a bit about video editing, I thought it might be nice to have the timestamps of all video frames in advance, i.e. without decoding the whole file. With that you can e.g. seek to the Nth frame or for a given absolute time you can find the closest video frame even for variable framerate files.<br /><br />The seeking functions always take a (scaled) time as argument and I saw no reason to change that.<br /><br />So what I needed was a table which allows the translation of framecounts to/from PTS values even for variable framerate files. For many fileformats a similar information is already stored internally (as a file index) so the task was only to convert the info into something usable and export it through the public API.<br /><br />Since this feature is very generic and might get used in both gmerlin and gmerlin-avdecoder, I decided to put the stuff into gavl.<br /><br /><b>Implicit table compression</b><br />One very smart feature in the Quicktime format is the stts atom. That's because it stores a table of frame_count/frame_duration pairs. The nice thing is, that for constant framerate files (the vast majority) the table consists of just one entry and translating framecounts to/from timestamps becomes trivial. Only for variable framerate streams, the table has more entries and the translation functions need longer.<br /><br /><b>The implementation</b><br />The frame table is called <code>gavl_frame_table_t</code> and is defined in <code>gavl.h</code>. <i>Note: The structure is public at present but might become private before the next public release.</i><br />Here, there are also the translation functions <code>gavl_frame_table_frame_to_time()</code>, <code>gavl_frame_table_time_to_frame()</code> and <code>gavl_frame_table_num_frames()</code>.<br /><br />If you use gmerlin-avdecoder for decoding files, you can use <code>bgav_get_frame_table()</code> to obtain a frame table of a video stream. It can be called after the stream has been fully initialized. Naturally you will want to use sample accurate decoding mode before obtaining the frame table. If you want to reuse the frame table after the file was closed, use <code>gavl_frame_table_save()</code> and <code>gavl_frame_table_load()</code>.<br /><br /><b>Future extension</b><br />An also interesting information are the timecodes. First of all, what's the difference between timecodes and timestamps in gmerlin terminology? Timestamps are used to synchronize multiple streams (audio, video, subtitles) of a file with each other for playback. They usually start at zero or another small value and play an important role e.g. after seeking in the files. Timecodes are usually given for each video frame and are used to identify scenes in a bunch of footage. They can resemble e.g. recording time/date.<br /><br />Timecodes will also be supported by the frame table, but this isn't implemented yet.burkhardhttp://www.blogger.com/profile/17023643103552829447noreply@blogger.com0tag:blogger.com,1999:blog-2134600781755905611.post-37644919868291262052009-09-15T01:14:00.003+02:002009-09-15T01:26:52.098+02:00Back from MaltaAfter a number of adventure- and/or conference trips, I decided to make a more ordinary vacation. Since the goal was to extend the summer a bit, we decided to travel in the first 2 September weeks (when German weather can already start to suck) and move to the southernmost country, which can be reached without many difficulties. Easy traveling for Europeans means to stay within the EU (and <a href="http://en.wikipedia.org/wiki/Euro">Euro</a> zone), so the destination was chosen to be <a href="http://en.wikipedia.org/wiki/Malta">Malta</a>.<br /><br />I knew a bit about Malta from TV documentaries, and while reading the Wikipedia article a bit, I became even more curious.<br /><br />Here are some facts I figured out. For pictures click on the Wikipedia links, they have higher quality than the ones from my crappy mobile.<br /><ul><li>It was damn hot and humid. Not all people from the Northern countries can withstand that. I was happy to find out that after surviving 4 weeks in the monsoon season in Southern India, I'm more heat resistant than my Greek companion.</li><li>It is indeed very stress-free because everyone speaks English (unlike in other Southern European countries) and the islands are small enough to reach practically every destination by bus. Just make sure you stay in the capital <a href="http://en.wikipedia.org/wiki/Valletta">Valletta</a>, almost all bus routes start there. On the smaller island <a href="http://en.wikipedia.org/wiki/Gozo">Gozo</a>, all busses start in <a href="http://en.wikipedia.org/wiki/Victoria,_Malta">Victoria</a>.<br /></li><li>It has an extremely rich 7000 years old history. You can visit <a href="http://en.wikipedia.org/wiki/Megalithic_Temples_of_Malta">prehistoric temples</a> (probably the oldest of their kind worldwide), churches, fortresses and remains from the <a href="http://en.wikipedia.org/wiki/Ancient_Rome">Romans</a> and <a href="http://en.wikipedia.org/wiki/Phoenicia">Phoenicians</a>. I already saw many churches from the inside before, I was not really a fan of Latin language and Roman history in school, and my companion didn't want to bother looking for sight seeing destinations at all. So I decided to concentrate on the prehistoric stuff.<br /></li><li>We saw the temples of <a href="http://en.wikipedia.org/wiki/%C4%A6a%C4%A1ar_Qim">Ħaġar Qim</a>, <a href="http://en.wikipedia.org/wiki/Mnajdra">Mnajdra</a> (both covered with giant protective tents now), <a href="http://en.wikipedia.org/wiki/Tarxien_Temples">Tarxien</a> and <a href="http://en.wikipedia.org/wiki/%C4%A0gantija">Ġgantija</a>. The latter ones were the most impressive, especially after we reached them after a long walk in the afternoon heat 20 minutes before the last admission :)<br /></li><li>The temples of <a href="http://en.wikipedia.org/wiki/Skorba_Temples">Skorba</a> are the oldest ones, but not very spectacular and not accessible for the public.<br /></li><li>No chance to get into the <a href="http://en.wikipedia.org/wiki/Hypogeum_of_%C4%A6al-Saflieni">Hypogeum of Ħal-Saflieni</a>. It was booked out 4 weeks in advance. That sucked.<br /></li><li>If you like beaches, Malta is definitely not the #1 destination for you. There are just a few, a nice one we visited is <a href="http://en.wikipedia.org/wiki/Ramla_Bay">Ramla Bay</a>.<br /></li><li>If you like diving at cliffs, Malta is a paradise for you.<br /></li><li>Funny is the bit of British culture (Malta was British until 1964). People are driving on the left, you get chips with most meals, and the phone booths and mailboxes are red, like in the UK.<br /></li><li>Some Maltese people know more German soccer teams than me.<br /></li></ul>Shortly after coming back home, German weather started to suck.burkhardhttp://www.blogger.com/profile/17023643103552829447noreply@blogger.com0tag:blogger.com,1999:blog-2134600781755905611.post-52071843839422640852009-08-14T17:56:00.003+02:002009-08-14T18:06:22.640+02:00Quick & dirty fix for the latest linux NULL pointer vulnerability<a href="http://lwn.net/Articles/347006/">This</a> one is pretty scary. It is the result of several flaws in SELinux, pulseaudio and some obscure network protocols. Proper fixing of this would require work at many places in the code.<br /><br />Up to now, Ubuntu doesn't have a patched kernel. In the meantime, place the following into the modprobe configuration:<br /><pre>install appletalk /bin/true<br />install ipx /bin/true<br />install irda /bin/true<br />install x25 /bin/true<br />install pppox /bin/true<br />install bluetooth /bin/true<br />install sctp /bin/true<br />install ax25 /bin/true<br /></pre>Then either unload these modules by hand (if they are loaded) or reboot the machine. One some systems I had to uninstall bluetooth support, which wasn't needed anyway. Naturally these protocols will stop working, but fortunately the exploit will stop working either :)burkhardhttp://www.blogger.com/profile/17023643103552829447noreply@blogger.com0