root/src/add-ons/media/media-add-ons/dvb/DVBMediaNode.cpp
/*
 * Copyright (c) 2004-2007 Marcus Overhagen <marcus@overhagen.de>
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, 
 * merge, publish, distribute, sublicense, and/or sell copies of 
 * the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be 
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 
 * OTHER DEALINGS IN THE SOFTWARE.
 */


#include <fcntl.h>
#include <malloc.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/uio.h>
#include <unistd.h>

#include <MediaRoster.h>
#include <Buffer.h>
#include <BufferGroup.h>
#include <ParameterWeb.h>
#include <TimeSource.h>
#include <String.h>
#include <Autolock.h>
#include <Debug.h>

#include <Directory.h>
#include <Entry.h>
#include <Path.h>

#include "MediaFormat.h"
#include "Packet.h"
#include "PacketQueue.h"
#include "pes.h"
#include "config.h"

//#define DUMP_VIDEO
//#define DUMP_AUDIO
//#define DUMP_RAW_AUDIO
//#define DUMP_MPEG_TS


#include "DVBMediaNode.h"

#define ENABLE_TRACE
//#define ENABLE_TRACE_TIMING

#undef TRACE

#ifdef ENABLE_TRACE
        #define TRACE printf
#else
        #define TRACE(a...)
#endif

#ifdef ENABLE_TRACE_TIMING
        #define TRACE_TIMING printf
#else
        #define TRACE_TIMING(a...)
#endif
                
#define RETURN_IF_ERROR(expr) { status_t e = (expr); if (e != B_OK) return e; }

#define ID_RAW_VIDEO    0
#define ID_RAW_AUDIO    1
#define ID_ENC_VIDEO    2
#define ID_ENC_AUDIO    3
#define ID_TS                   4

// Timeouts for requesting buffers, if the system is busy,
// the output buffer queue is full, requesting a buffer will
// timeout, and we need to drop the current data
#define VIDEO_BUFFER_REQUEST_TIMEOUT    20000   
#define AUDIO_BUFFER_REQUEST_TIMEOUT    10000

// DVB data arrives early and with a timestamp, this is used to validate
// that the timestamp is correct and we don't get stuck
#define VIDEO_MAX_EARLY                                 3000000
        // up to 3 seconds too early
#define VIDEO_MAX_LATE                                  50000
        // no more than 50 ms too late
#define AUDIO_MAX_EARLY                                 3000000
        // up to 3 seconds too early
#define AUDIO_MAX_LATE                                  50000
        // no more than 50 ms too late

#define PROCESSING_LATENCY                              1500
        // assumed latency for sending the buffer

#define STOP_CAPTURE_WHILE_TUNING               1

#define M_REFRESH_PARAMETER_WEB                 (BTimedEventQueue::B_USER_EVENT + 1)


DVBMediaNode::DVBMediaNode(
        BMediaAddOn *addon, const char *name,
        int32 internal_id, DVBCard *card)
 :      BMediaNode(name)
 ,      BBufferProducer(B_MEDIA_RAW_VIDEO)
 ,      BControllable()
 ,      BMediaEventLooper()
 ,      fStopDisabled(false)
 ,      fOutputEnabledRawVideo(false)
 ,      fOutputEnabledRawAudio(false)
 ,      fOutputEnabledEncVideo(false)
 ,      fOutputEnabledEncAudio(false)
 ,      fOutputEnabledTS(false)
 ,      fCardDataQueue(new PacketQueue(6))
 ,      fRawVideoQueue(new PacketQueue(56))
 ,      fRawAudioQueue(new PacketQueue(56))
 ,      fEncVideoQueue(new PacketQueue(56))
 ,      fEncAudioQueue(new PacketQueue(56))
 ,      fMpegTsQueue(new PacketQueue(16))
 ,      fCard(card)
 ,      fCaptureThreadsActive(false)
 ,      fThreadIdCardReader(-1)
 ,      fThreadIdMpegDemux(-1)
 ,      fThreadIdRawAudio(-1)
 ,      fThreadIdRawVideo(-1)
 ,      fThreadIdEncAudio(-1)
 ,      fThreadIdEncVideo(-1)
 ,      fThreadIdMpegTS(-1)
 ,      fTerminateThreads(false)
 ,      fDemux(new TransportStreamDemux(fRawVideoQueue, fRawAudioQueue,
                fEncVideoQueue, fEncAudioQueue, fMpegTsQueue))
 ,      fBufferGroupRawVideo(0)
 ,      fBufferGroupRawAudio(0)
 ,      fInterfaceType(DVB_TYPE_UNKNOWN)
 ,      fAudioPid(-1)
 ,      fVideoPid(-1)
 ,      fPcrPid(-1)
 ,      fTuningSuccess(false)
 ,      fCaptureActive(false)
 ,      fVideoDelaySem(create_sem(0, "video delay sem"))
 ,      fAudioDelaySem(create_sem(0, "audio delay sem"))
 ,      fSelectedState(-1)
 ,      fSelectedRegion(-1)
 ,      fSelectedChannel(-1)
 ,      fSelectedAudio(-1)
 ,      fStateList(new StringList)
 ,      fRegionList(new StringList)
 ,      fChannelList(new StringList)
 ,      fAudioList(new StringList)
 ,      fVideoDecoder(0)
 ,      fAudioDecoder(0)
 ,      fCurrentVideoPacket(0)
 ,      fCurrentAudioPacket(0)
{
        TRACE("DVBMediaNode::DVBMediaNode\n");

        AddNodeKind(B_PHYSICAL_INPUT);

        fInternalID = internal_id;
        fAddOn = addon;
        
        fInitStatus = B_OK;
        
        InitDefaultFormats();

        // in the beginning, the required formats are the same as the defaults
        fRequiredFormatRawVideo = fDefaultFormatRawVideo;
        fRequiredFormatRawAudio = fDefaultFormatRawAudio;
        fRequiredFormatEncVideo = fDefaultFormatEncVideo;
        fRequiredFormatEncAudio = fDefaultFormatEncAudio;
        fRequiredFormatTS = fDefaultFormatTS;

        TRACE("current RunMode = %d\n", RunMode());

#ifdef DUMP_VIDEO
        fVideoFile = open("/boot/home/dvb-video.mpg", O_RDWR | O_CREAT | O_TRUNC);
#endif
#ifdef DUMP_AUDIO
        fAudioFile = open("/boot/home/dvb-audio.mpg", O_RDWR | O_CREAT | O_TRUNC);
#endif
#ifdef DUMP_RAW_AUDIO
        fRawAudioFile = open("/boot/home/dvb-audio.raw",
                O_RDWR | O_CREAT | O_TRUNC);
#endif
#ifdef DUMP_MPEG_TS
        fMpegTsFile = open("/boot/home/dvb-mpeg.ts", O_RDWR | O_CREAT | O_TRUNC);
#endif
}


DVBMediaNode::~DVBMediaNode()
{
        TRACE("DVBMediaNode::~DVBMediaNode\n");
        
        StopCapture();
        
        delete_sem(fVideoDelaySem);
        delete_sem(fAudioDelaySem);

        // fCard is owned by the media addon
        delete fCardDataQueue;
        delete fRawVideoQueue;
        delete fRawAudioQueue;
        delete fEncVideoQueue;
        delete fEncAudioQueue;
        delete fMpegTsQueue;

        delete fDemux;

        delete fBufferGroupRawVideo;
        delete fBufferGroupRawAudio;

        delete fStateList;
        delete fRegionList;
        delete fChannelList;
        delete fAudioList;
        
#ifdef DUMP_VIDEO
        close(fVideoFile);
#endif
#ifdef DUMP_AUDIO
        close(fAudioFile);
#endif
#ifdef DUMP_RAW_AUDIO
        close(fRawAudioFile);
#endif
#ifdef DUMP_MPEG_TS
        close(fMpegTsFile);
#endif

}


/* BMediaNode */


BMediaAddOn *
DVBMediaNode::AddOn(int32 *internal_id) const
{
        if (internal_id)
                *internal_id = fInternalID;
        return fAddOn;
}


status_t 
DVBMediaNode::HandleMessage(int32 message, const void *data, size_t size)
{
        return B_ERROR;
}


void 
DVBMediaNode::Preroll()
{
        /* This hook may be called before the node is started to give the hardware
         * a chance to start. */
}


void
DVBMediaNode::SetTimeSource(BTimeSource *time_source)
{
        TRACE("DVBMediaNode::SetTimeSource\n");
        //printf("current RunMode = %d\n", RunMode());
        //printf("_m_recordDelay = %lld\n", _m_recordDelay);
}


void
DVBMediaNode::SetRunMode(run_mode mode)
{
        TRACE("DVBMediaNode::SetRunMode: %d\n", mode);
        TRACE("current RunMode = %d\n", RunMode());
        //printf("_m_recordDelay = %lld\n", _m_recordDelay);
}


/* BMediaEventLooper */


void 
DVBMediaNode::NodeRegistered()
{
        TRACE("DVBMediaNode::NodeRegistered\n");

        fOutputRawVideo.node = Node();
        fOutputRawVideo.source.port = ControlPort();
        fOutputRawVideo.source.id = ID_RAW_VIDEO;
        fOutputRawVideo.destination = media_destination::null;
        fOutputRawVideo.format = fDefaultFormatRawVideo;
        strcpy(fOutputRawVideo.name, SourceDefaultName(fOutputRawVideo.source));

        fOutputRawAudio.node = Node();
        fOutputRawAudio.source.port = ControlPort();
        fOutputRawAudio.source.id = ID_RAW_AUDIO;
        fOutputRawAudio.destination = media_destination::null;
        fOutputRawAudio.format = fDefaultFormatRawAudio;
        strcpy(fOutputRawAudio.name, SourceDefaultName(fOutputRawAudio.source));

        fOutputEncVideo.node = Node();
        fOutputEncVideo.source.port = ControlPort();
        fOutputEncVideo.source.id = ID_ENC_VIDEO;
        fOutputEncVideo.destination = media_destination::null;
        fOutputEncVideo.format = fDefaultFormatEncVideo;
        strcpy(fOutputEncVideo.name, SourceDefaultName(fOutputEncVideo.source));

        fOutputEncAudio.node = Node();
        fOutputEncAudio.source.port = ControlPort();
        fOutputEncAudio.source.id = ID_ENC_AUDIO;
        fOutputEncAudio.destination = media_destination::null;
        fOutputEncAudio.format = fDefaultFormatEncAudio;
        strcpy(fOutputEncAudio.name, SourceDefaultName(fOutputEncAudio.source));

        fOutputTS.node = Node();
        fOutputTS.source.port = ControlPort();
        fOutputTS.source.id = ID_TS;
        fOutputTS.destination = media_destination::null;
        fOutputTS.format = fDefaultFormatTS;
        strcpy(fOutputTS.name, SourceDefaultName(fOutputTS.source));

        fCard->GetCardType(&fInterfaceType);

        // set control thread priority  
        SetPriority(110);

        LoadSettings();
        
        RefreshParameterWeb();

        // this nodes operates in recording mode, so set it (will be done
        // asynchronously)
        BMediaRoster::Roster()->SetRunModeNode(Node(), B_RECORDING);
                // as it's a notification hook, calling this doesn't work:
                // SetRunMode(B_RECORDING);

        //printf("RunMode = %d\n", RunMode());
        //printf("_m_recordDelay = %lld\n", _m_recordDelay);

        Run();
}


void
DVBMediaNode::Stop(bigtime_t performance_time, bool immediate)
{
        if (fStopDisabled)
                return;
        else
                BMediaEventLooper::Stop(performance_time, immediate);
}


void 
DVBMediaNode::HandleEvent(const media_timed_event *event,
                bigtime_t lateness, bool realTimeEvent)
{

        switch(event->type)
        {
                case M_REFRESH_PARAMETER_WEB:
                        RefreshParameterWeb();
                        break;
                case BTimedEventQueue::B_START:
                        HandleStart(event->event_time);
                        break;
                case BTimedEventQueue::B_STOP:
                        HandleStop();
                        break;
                case BTimedEventQueue::B_WARP:
                        HandleTimeWarp(event->bigdata);
                        break;
                case BTimedEventQueue::B_SEEK:
                        HandleSeek(event->bigdata);
                        break;
                case BTimedEventQueue::B_HANDLE_BUFFER:
                case BTimedEventQueue::B_DATA_STATUS:
                case BTimedEventQueue::B_PARAMETER:
                default:
                        TRACE("DVBMediaNode::HandleEvent: Unhandled event -- %lx\n",
                                event->type);
                        break;
        }
}


/* BBufferProducer */


status_t 
DVBMediaNode::FormatChangeRequested(const media_source &source,
                const media_destination &destination, media_format *io_format,
                int32 *_deprecated_)
{
        TRACE("DVBMediaNode::FormatChangeRequested denied: %s\n",
                SourceDefaultName(source));
        return B_ERROR; 
}


status_t 
DVBMediaNode::GetNextOutput(int32 *cookie, media_output *out_output)
{
        switch (*cookie) {
                case 0: *out_output = fOutputRawVideo;  break;
                case 1: *out_output = fOutputRawAudio;  break;
                case 2: *out_output = fOutputEncVideo;  break;
                case 3: *out_output = fOutputEncAudio;  break;
                case 4: *out_output = fOutputTS;                break;
                default:                                                                return B_BAD_INDEX;
        }

        (*cookie) += 1;
        return B_OK;
}


status_t 
DVBMediaNode::DisposeOutputCookie(int32 cookie)
{
        return B_OK;
}


status_t 
DVBMediaNode::SetBufferGroup(const media_source &source, BBufferGroup *group)
{
        TRACE("DVBMediaNode::SetBufferGroup denied: %s\n",
                SourceDefaultName(source));
        return B_ERROR;
}


status_t 
DVBMediaNode::VideoClippingChanged(const media_source &for_source,
                int16 num_shorts, int16 *clip_data,
                const media_video_display_info &display, int32 *_deprecated_)
{
        return B_ERROR;
}


status_t 
DVBMediaNode::GetLatency(bigtime_t *out_latency)
{
        if (B_OK != BBufferProducer::GetLatency(out_latency))
                *out_latency = 50000;
                
        printf("DVBMediaNode::GetLatency: %lld\n", *out_latency);
        *out_latency += PROCESSING_LATENCY;
        return B_OK;
}


status_t 
DVBMediaNode::FormatSuggestionRequested(
                media_type type, int32 quality, media_format *format)
{
        TRACE("DVBMediaNode::FormatSuggestionRequested\n");
        
        switch (type) {
                case B_MEDIA_RAW_VIDEO:
                        *format = fDefaultFormatRawVideo;
                        break;

                case B_MEDIA_RAW_AUDIO:
                        *format = fDefaultFormatRawAudio;
                        break;

                case B_MEDIA_ENCODED_VIDEO:
                        *format = fDefaultFormatEncVideo;
                        break;

                case B_MEDIA_ENCODED_AUDIO:
                        *format = fDefaultFormatEncAudio;
                        break;

                case B_MEDIA_MULTISTREAM:
                        *format = fDefaultFormatTS;
                        break;

                default:
                        TRACE("Bad type!\n");
                        return B_MEDIA_BAD_FORMAT;
        }

        #ifdef DEBUG
                TRACE("suggested format: ");
                PrintFormat(*format);
        #endif

        return B_OK;
}


status_t 
DVBMediaNode::FormatProposal(const media_source &source, media_format *format)
{
        TRACE("DVBMediaNode::FormatProposal: %s\n", SourceDefaultName(source));
        
        /* The connection process:
         * we are here => BBufferProducer::FormatProposal
         *                BBufferConsumer::AcceptFormat
         *                BBufferProducer::PrepareToConnect
         *                BBufferConsumer::Connected
         *                BBufferProducer::Connect
         *
         * What we need to do:
         * - if the format contains a wildcard AND we have a requirement for that
         *   field, set it to the value we need.
         * - if a field has a value that is not wildcard and not supported by us,
         *   we don't change it, and return B_MEDIA_BAD_FORMAT
         * - after we are done, the format may still contain wildcards.
         */
        
        if (source.port != ControlPort())
                goto _bad_source;

        #ifdef DEBUG
                TRACE("proposed format: ");
                PrintFormat(*format);
                TRACE("required format: ");
                switch (source.id) {
                        case ID_RAW_VIDEO:
                                PrintFormat(fRequiredFormatRawVideo);
                                break;

                        case ID_RAW_AUDIO:
                                PrintFormat(fRequiredFormatRawAudio);
                                break;

                        case ID_ENC_VIDEO:
                                PrintFormat(fRequiredFormatEncVideo);
                                break;

                        case ID_ENC_AUDIO:
                                PrintFormat(fRequiredFormatEncAudio);
                                break;

                        case ID_TS:
                                PrintFormat(fRequiredFormatTS);
                                break;
                }
        #endif

        switch (source.id) {
                case ID_RAW_VIDEO:
                        // check if destination still available
                        if (fOutputRawVideo.destination != media_destination::null)
                                goto _bad_source;
                        // set requirements and check if compatible
                        color_space c;
                        c = format->u.raw_video.display.format;
                        format->SpecializeTo(&fRequiredFormatRawVideo);
                        format->u.raw_video.display.format = c;
//                      if (!format->Matches(&fRequiredFormatRawVideo))
//                              goto _bad_format_1;
                        if (!VerifyFormatRawVideo(format->u.raw_video))
                                goto _bad_format_2;
                        break;

                case ID_RAW_AUDIO:
                        // check if destination still available
                        if (fOutputRawAudio.destination != media_destination::null)
                                goto _bad_source;
                        // set requirements and check if compatible
                        format->SpecializeTo(&fRequiredFormatRawAudio);
                        if (!format->Matches(&fRequiredFormatRawAudio))
                                goto _bad_format_1;
                        if (!VerifyFormatRawAudio(format->u.raw_audio))
                                goto _bad_format_2;
                        break;

                case ID_ENC_VIDEO:
                        // check if destination still available
                        if (fOutputEncVideo.destination != media_destination::null)
                                goto _bad_source;
                        // set requirements and check if compatible
                        format->SpecializeTo(&fRequiredFormatEncVideo);
                        if (!format->Matches(&fRequiredFormatEncVideo))
                                goto _bad_format_1;
                        break;

                case ID_ENC_AUDIO:
                        // check if destination still available
                        if (fOutputEncAudio.destination != media_destination::null)
                                goto _bad_source;
                        // set requirements and check if compatible
                        format->SpecializeTo(&fRequiredFormatEncAudio);
                        if (!format->Matches(&fRequiredFormatEncAudio))
                                goto _bad_format_1;
                        break;

                case ID_TS:
                        // check if destination still available
                        if (fOutputTS.destination != media_destination::null)
                                goto _bad_source;
                        // set requirements and check if compatible
                        format->SpecializeTo(&fRequiredFormatTS);
                        if (!format->Matches(&fRequiredFormatTS))
                                goto _bad_format_1;
                        break;

                default:
                        goto _bad_source;
        }

        #ifdef DEBUG
                TRACE("final format: ");
                PrintFormat(*format);
        #endif
        
        return B_OK;

_bad_source:
        TRACE("Error: bad source!\n");
        return B_MEDIA_BAD_SOURCE;

_bad_format_1:
        TRACE("Error, bad format (1): ");
        goto _bad_format;

_bad_format_2:
        TRACE("Error, bad format (2): ");
        goto _bad_format;

_bad_format:
        #ifdef DEBUG
                PrintFormat(*format);
        #endif
        return B_MEDIA_BAD_FORMAT;
}


status_t 
DVBMediaNode::PrepareToConnect(const media_source &source,
                const media_destination &destination, media_format *format,
                media_source *out_source, char *out_name)
{
        /* The connection process:
         *                BBufferProducer::FormatProposal
         *                BBufferConsumer::AcceptFormat
         * we are here => BBufferProducer::PrepareToConnect
         *                BBufferConsumer::Connected
         *                BBufferProducer::Connect
         *
         * At this point, the consumer's AcceptFormat() method has been called,
         * and that node has potentially changed the proposed format. It may
         * also have left wildcards in the format. PrepareToConnect()
         * *must* fully specialize the format before returning!
         */

        TRACE("DVBMediaNode::PrepareToConnect: %s\n", SourceDefaultName(source));

        #ifdef DEBUG
                TRACE("connecting format: ");
                PrintFormat(*format);
                TRACE("required format: ");
                switch (source.id) {
                        case ID_RAW_VIDEO:
                                PrintFormat(fRequiredFormatRawVideo);
                                break;

                        case ID_RAW_AUDIO:
                                PrintFormat(fRequiredFormatRawAudio);
                                break;

                        case ID_ENC_VIDEO:
                                PrintFormat(fRequiredFormatEncVideo);
                                break;

                        case ID_ENC_AUDIO:
                                PrintFormat(fRequiredFormatEncAudio);
                                break;

                        case ID_TS:
                                PrintFormat(fRequiredFormatTS);
                                break;
                }
        #endif
        
        // is the source valid?
        if (source.port != ControlPort())
                goto _bad_source;

        // 1) check if the output is still available,
        // 2) specialize and verify the format
        switch (source.id) {
                case ID_RAW_VIDEO:
                        if (fOutputRawVideo.destination != media_destination::null)
                                goto _already_connected;
                        SpecializeFormatRawVideo(&format->u.raw_video);
//                      if (!format->Matches(&fRequiredFormatRawVideo))
//                              goto _bad_format;
                        if (!VerifyFormatRawVideo(format->u.raw_video))
                                goto _bad_format;
                        break;

                case ID_RAW_AUDIO:
                        if (fOutputRawAudio.destination != media_destination::null)
                                goto _already_connected;
                        SpecializeFormatRawAudio(&format->u.raw_audio);
                        if (!format->Matches(&fRequiredFormatRawAudio))
                                goto _bad_format;
                        if (!VerifyFormatRawAudio(format->u.raw_audio))
                                goto _bad_format;
                        break;

                case ID_ENC_VIDEO:
                        if (fOutputEncVideo.destination != media_destination::null)
                                goto _already_connected;
                        SpecializeFormatEncVideo(&format->u.encoded_video);
                        if (!format->Matches(&fRequiredFormatEncVideo))
                                goto _bad_format;
                        break;

                case ID_ENC_AUDIO:
                        if (fOutputEncAudio.destination != media_destination::null)
                                goto _already_connected;
                        SpecializeFormatEncAudio(&format->u.encoded_audio);
                        if (!format->Matches(&fRequiredFormatRawVideo))
                                goto _bad_format;
                        break;

                case ID_TS:
                        if (fOutputTS.destination != media_destination::null)
                                goto _already_connected;
                        SpecializeFormatTS(&format->u.multistream);
                        if (!format->Matches(&fRequiredFormatTS))
                                goto _bad_format;
                        break;

                default:
                        goto _bad_source;
        }

        #ifdef DEBUG
                TRACE("final format: ");
                PrintFormat(*format);
        #endif

        // reserve the connection by setting destination
        // set the output's format to the new format
        SetOutput(source, destination, *format);

        // set source and suggest a name
        *out_source = source;
        strcpy(out_name, SourceDefaultName(source));
        
        return B_OK;

_bad_source:
        TRACE("Error: bad source!\n");
        return B_MEDIA_BAD_SOURCE;

_bad_format:
        #ifdef DEBUG
                TRACE("Error, bad format: ");
                PrintFormat(*format);
        #endif
        return B_MEDIA_BAD_FORMAT;

_already_connected:
        TRACE("Error: already connected!\n");
        return B_MEDIA_ALREADY_CONNECTED;
}


void 
DVBMediaNode::Connect(status_t error, const media_source &source,
                const media_destination &destination, const media_format &format,
                char *io_name)
{
        /* The connection process:
         *                BBufferProducer::FormatProposal
         *                BBufferConsumer::AcceptFormat
         *                BBufferProducer::PrepareToConnect
         *                BBufferConsumer::Connected
         * we are here => BBufferProducer::Connect
         */

        TRACE("DVBMediaNode::Connect: %s\n", SourceDefaultName(source));

        if (error != B_OK) {
                TRACE("Error during connecting\n");
                // if an error occured, unreserve the connection
                ResetOutput(source);
                return;
        }

        #ifdef DEBUG
                TRACE("connected format: ");
                PrintFormat(format);
        #endif

        // Since the destination is allowed to be changed by the
        // consumer, the one we got in PrepareToConnect() is no
        // longer correct, and must be updated here.
        SetOutput(source, destination, format);

        // Set output as connected
        switch (source.id) {
                case ID_RAW_VIDEO:
                        fOutputEnabledRawVideo = true;
                        break;

                case ID_RAW_AUDIO:
                        fOutputEnabledRawAudio = true;
                        break;

                case ID_ENC_VIDEO:
                        fOutputEnabledEncVideo = true;
                        break;

                case ID_ENC_AUDIO:
                        fOutputEnabledEncAudio = true;
                        break;

                case ID_TS:
                        fOutputEnabledTS = true;
                        break;

                default:
                        break;
        }

        // if the connection has no name, we set it now
        if (strlen(io_name) == 0)
                strcpy(io_name, SourceDefaultName(source));

        #ifdef DEBUG
                bigtime_t latency;
                media_node_id ts;
                if (B_OK != FindLatencyFor(destination, &latency, &ts))
                        TRACE("FindLatencyFor failed\n");
                else
                        TRACE("downstream latency %lld\n", latency);
        #endif
}


void 
DVBMediaNode::Disconnect(const media_source &source,
                const media_destination &destination)
{
        TRACE("DVBMediaNode::Disconnect: %s\n", SourceDefaultName(source));
        
        // unreserve the connection
        ResetOutput(source);

        // Set output to disconnected
        switch (source.id) {
                case ID_RAW_VIDEO:
                        fOutputEnabledRawVideo = false;
                        break;

                case ID_RAW_AUDIO:
                        fOutputEnabledRawAudio = false;
                        break;

                case ID_ENC_VIDEO:
                        fOutputEnabledEncVideo = false;
                        break;

                case ID_ENC_AUDIO:
                        fOutputEnabledEncAudio = false;
                        break;

                case ID_TS:
                        fOutputEnabledTS = false;
                        break;

                default:
                        break;
        }
}


void 
DVBMediaNode::LateNoticeReceived(const media_source &source,
                bigtime_t how_much, bigtime_t performance_time)
{
        TRACE("DVBMediaNode::LateNoticeReceived %lld late at %lld\n", how_much,
                performance_time);
}


void 
DVBMediaNode::EnableOutput(const media_source &source, bool enabled,
                int32 *_deprecated_)
{
        TRACE("DVBMediaNode::EnableOutput id = %ld, enabled = %d\n", source.id,
                enabled);
        /* not used yet
        switch (source.id) {
                case ID_RAW_VIDEO:      fOutputEnabledRawVideo = enabled; break;
                case ID_RAW_AUDIO:      fOutputEnabledRawAudio = enabled; break;
                case ID_ENC_VIDEO:      fOutputEnabledEncVideo = enabled; break;
                case ID_ENC_AUDIO:      fOutputEnabledEncAudio = enabled; break;
                case ID_TS:                     fOutputEnabledTS = enabled; break;
                default:                        break;
        }
        */
}


status_t 
DVBMediaNode::SetPlayRate(int32 numer, int32 denom)
{

        return B_ERROR;
}


void 
DVBMediaNode::AdditionalBufferRequested(const media_source &source,
                media_buffer_id prev_buffer, bigtime_t prev_time,
                const media_seek_tag *prev_tag)
{
        TRACE("DVBMediaNode::AdditionalBufferRequested: %s\n",
                SourceDefaultName(source));
}


void 
DVBMediaNode::LatencyChanged(const media_source &source,
                const media_destination &destination, bigtime_t new_latency,
                uint32 flags)
{
        TRACE("DVBMediaNode::LatencyChanged to %lld\n", new_latency);
}

/* DVBMediaNode */


void
DVBMediaNode::HandleTimeWarp(bigtime_t performance_time)
{
        TRACE("DVBMediaNode::HandleTimeWarp at %lld\n", performance_time);
}


void
DVBMediaNode::HandleSeek(bigtime_t performance_time)
{
        TRACE("DVBMediaNode::HandleSeek at %lld\n", performance_time);
}


void
DVBMediaNode::InitDefaultFormats()
{
        // 720 * 576
        fDefaultFormatRawVideo.type = B_MEDIA_RAW_VIDEO;
        fDefaultFormatRawVideo.u.raw_video.display.format = B_RGB32;
        fDefaultFormatRawVideo.u.raw_video.display.line_width = 720;
        fDefaultFormatRawVideo.u.raw_video.display.line_count = 576;
        fDefaultFormatRawVideo.u.raw_video.last_active
                = fDefaultFormatRawVideo.u.raw_video.display.line_count - 1;
        fDefaultFormatRawVideo.u.raw_video.display.bytes_per_row
                = fDefaultFormatRawVideo.u.raw_video.display.line_width * 4;
        fDefaultFormatRawVideo.u.raw_video.field_rate = 0;
                // wildcard
        fDefaultFormatRawVideo.u.raw_video.interlace = 1;
        fDefaultFormatRawVideo.u.raw_video.first_active = 0;
        fDefaultFormatRawVideo.u.raw_video.orientation = B_VIDEO_TOP_LEFT_RIGHT;
        fDefaultFormatRawVideo.u.raw_video.pixel_width_aspect = 1;
        fDefaultFormatRawVideo.u.raw_video.pixel_height_aspect = 1;
        fDefaultFormatRawVideo.u.raw_video.display.pixel_offset = 0;
        fDefaultFormatRawVideo.u.raw_video.display.line_offset = 0;
        fDefaultFormatRawVideo.u.raw_video.display.flags = 0;

        fDefaultFormatRawAudio.type = B_MEDIA_RAW_AUDIO;
        fDefaultFormatRawAudio.u.raw_audio.frame_rate = 48000;
        fDefaultFormatRawAudio.u.raw_audio.channel_count = 2;
//  XXX broken in Haiku...
//      fDefaultFormatRawAudio.u.raw_audio.format = 0; // wildcard
        fDefaultFormatRawAudio.u.raw_audio.format
                = media_raw_audio_format::B_AUDIO_SHORT;
//  when set to 0, haiku mixer has problems when diung a format change
//  set to short and debug the buffer_size problem first!
        fDefaultFormatRawAudio.u.raw_audio.byte_order = B_MEDIA_HOST_ENDIAN;
//      fDefaultFormatRawAudio.u.raw_audio.buffer_size = 0; // wildcard
//      fDefaultFormatRawAudio.u.raw_audio.buffer_size = 0x1200;
//      fDefaultFormatRawAudio.u.raw_audio.buffer_size = 0x1000;
        fDefaultFormatRawAudio.u.raw_audio.buffer_size = 32768;
//      fDefaultFormatRawAudio.u.raw_audio.buffer_size = 333 * 8;
//      fDefaultFormatRawAudio.u.raw_audio.buffer_size = 512;
//  when set to anything different from 32768 haiku mixer has problems

        fDefaultFormatEncVideo.type = B_MEDIA_ENCODED_VIDEO;
        fDefaultFormatEncAudio.type = B_MEDIA_ENCODED_AUDIO;
        fDefaultFormatTS.type = B_MEDIA_MULTISTREAM;
}


bool
DVBMediaNode::VerifyFormatRawVideo(const media_raw_video_format &format)
{
        if (format.display.format != 0 &&
                format.display.format != B_RGB32 &&
                format.display.format != B_YCbCr422)
                return false;
                
        return true;
}


bool
DVBMediaNode::VerifyFormatRawAudio(const media_multi_audio_format &format)
{
        if (format.format != 0 && 
                format.format != media_raw_audio_format::B_AUDIO_FLOAT &&
                format.format != media_raw_audio_format::B_AUDIO_SHORT)
                return false;

        return true;
}


void
DVBMediaNode::SpecializeFormatRawVideo(media_raw_video_format *format)
{
        // Here we need to specialize *all* remaining wildcard
        // fields that the consumer didn't set
        if (format->field_rate == 0.0)
                format->field_rate = 25;
                
        // XXX a lot is missing here...
}


void
DVBMediaNode::SpecializeFormatRawAudio(media_multi_audio_format *format)
{
        // Here we need to specialize *all* remaining wildcard
        // fields that the consumer didn't set
        if (format->format == 0)
                format->format = media_raw_audio_format::B_AUDIO_SHORT;

        if (format->buffer_size == 0)
                format->buffer_size = 333 * 8;

        // XXX a lot is missing here...
}


void
DVBMediaNode::SpecializeFormatEncVideo(media_encoded_video_format *format)
{
        // Here we need to specialize *all* remaining wildcard
        // fields that the consumer didn't set
}


void
DVBMediaNode::SpecializeFormatEncAudio(media_encoded_audio_format *format)
{
        // Here we need to specialize *all* remaining wildcard
        // fields that the consumer didn't set
}


void
DVBMediaNode::SpecializeFormatTS(media_multistream_format *format)
{
        // Here we need to specialize *all* remaining wildcard
        // fields that the consumer didn't set
}


status_t
DVBMediaNode::SetOutput(const media_source &source, 
        const media_destination &destination, const media_format &format)
{
        switch (source.id) {
                case ID_RAW_VIDEO:
                        fOutputRawVideo.destination = destination;
                        fOutputRawVideo.format = format;
                        break;

                case ID_RAW_AUDIO:
                        fOutputRawAudio.destination = destination;
                        fOutputRawAudio.format = format;
                        break;

                case ID_ENC_VIDEO:
                        fOutputEncVideo.destination = destination;
                        fOutputEncVideo.format = format;
                        break;

                case ID_ENC_AUDIO:
                        fOutputEncAudio.destination = destination;
                        fOutputEncAudio.format = format;
                        break;

                case ID_TS:
                        fOutputTS.destination = destination;
                        fOutputTS.format = format;
                        break;

                default:
                        return B_MEDIA_BAD_SOURCE;
        }
        return B_OK;
}


status_t
DVBMediaNode::ResetOutput(const media_source &source)
{
        switch (source.id) {
                case ID_RAW_VIDEO:
                        fOutputRawVideo.destination = media_destination::null;
                        fOutputRawVideo.format = fDefaultFormatRawVideo;
                        break;

                case ID_RAW_AUDIO:
                        fOutputRawAudio.destination = media_destination::null;
                        fOutputRawAudio.format = fDefaultFormatRawAudio;
                        break;
                                
                case ID_ENC_VIDEO:
                        fOutputEncVideo.destination = media_destination::null;
                        fOutputEncVideo.format = fDefaultFormatEncVideo;
                        break;
                                
                case ID_ENC_AUDIO:
                        fOutputEncAudio.destination = media_destination::null;
                        fOutputEncAudio.format = fDefaultFormatEncAudio;
                        break;
                                
                case ID_TS:
                        fOutputTS.destination = media_destination::null;
                        fOutputTS.format = fDefaultFormatTS;
                        break;
                                
                default:
                        return B_MEDIA_BAD_SOURCE;
        }
        return B_OK;
}


const char *
DVBMediaNode::SourceDefaultName(const media_source &source)
{
        switch (source.id) {
                case ID_RAW_VIDEO:
                        return "raw video";

                case ID_RAW_AUDIO:
                        return "raw audio";

                case ID_ENC_VIDEO:
                        return "encoded video";

                case ID_ENC_AUDIO:
                        return "encoded audio";

                case ID_TS:
                        return "MPEG2 TS";

                default:
                        return NULL;
        }
}


void
DVBMediaNode::HandleStart(bigtime_t performance_time)
{
        TRACE("DVBMediaNode::HandleStart\n");
        StartCapture();
}


void
DVBMediaNode::HandleStop(void)
{
        TRACE("DVBMediaNode::HandleStop\n");
        StopCapture();
}       


status_t
DVBMediaNode::Tune()
{
        TRACE("DVBMediaNode::Tune enter\n");

        TRACE("state %d, region %d, channel %d, audio %d\n",
                fSelectedState, fSelectedRegion, fSelectedChannel, fSelectedAudio);

        status_t err;
        bool needs_tuning = false;

        if (fSelectedChannel < 0 || fSelectedAudio < 0) {
                printf("DVBMediaNode::Tune: invalid tuning info\n");
                StopCapture();
                err = B_ERROR;
                goto end;
//              return B_ERROR;
        }
        
        const char *desc;
        
        dvb_tuning_parameters_t new_params;
        int new_video_pid;
        int new_audio_pid;
        int new_pcr_pid;

        desc = fChannelList->ItemAt(fSelectedChannel);
        err = ExtractTuningParams(desc, fSelectedAudio, &new_params,
                &new_video_pid, &new_audio_pid, &new_pcr_pid);

        if (err != B_OK) {
                printf("DVBMediaNode::Tune: getting tuning info failed\n");
                StopCapture();
                err = B_ERROR;
                goto end;
//              return B_ERROR;
        }
/*      
        if (fTuningParam.frequency == new_params.frequency) {
                printf("DVBMediaNode::Tune: frequency not changed\n");
                fVideoPid = new_video_pid;
                fAudioPid = new_audio_pid;
                fPcrPid = new_pcr_pid;
                fDemux->SetPIDs(fVideoPid, fAudioPid, fPcrPid);
                return B_OK;
        }
*/      
        switch (fInterfaceType) {
                case DVB_TYPE_DVB_T:
                        needs_tuning = (fTuningParam.u.dvb_t.frequency
                                        != new_params.u.dvb_t.frequency) || !fTuningSuccess;
                        needs_tuning = true;
                        break;

                case DVB_TYPE_DVB_S:
                        printf("needs_tuning = %d, forcing tuning for DVB-S\n",
                                needs_tuning);
                        needs_tuning = true;
                        break;

                default:
                        needs_tuning = true;
                        break;
        }
                
        fTuningParam = new_params;
        fVideoPid = new_video_pid;
        fAudioPid = new_audio_pid;
        fPcrPid = new_pcr_pid;
        
        if (needs_tuning) {
printf("about to stop capture 1\n");
#if STOP_CAPTURE_WHILE_TUNING
printf("about to stop capture 2\n");
                err = StopCapture();
                if (err) {
                        printf("Tune: StopCapture failed\n");
                        goto end;
                }
#endif
        } else {
#if STOP_CAPTURE_WHILE_TUNING
                StopCaptureThreads();
#endif
        }
        
        if (needs_tuning) {
                err = fCard->SetTuningParameter(fTuningParam);
                fTuningSuccess = err == B_OK;
        }

        fDemux->SetPIDs(fVideoPid, fAudioPid, fPcrPid);
        
        if (needs_tuning) {
                if (fTuningSuccess) {
                        fCard->GetTuningParameter(&fTuningParam);
                        err = StartCapture();
                }
        } else {
#if STOP_CAPTURE_WHILE_TUNING
                StartCaptureThreads();
#endif
        }
        
end:
        EventQueue()->AddEvent(media_timed_event(0, M_REFRESH_PARAMETER_WEB));
//      RefreshParameterWeb();

        TRACE("DVBMediaNode::Tune finished\n");
        return err;
}


status_t
DVBMediaNode::StartCapture()
{
        TRACE("DVBMediaNode::StartCapture\n");

        if (fCaptureActive)
                return B_OK;
                
        RETURN_IF_ERROR(StopCaptureThreads());
        
        if (!fTuningSuccess) {
                RETURN_IF_ERROR(Tune());
        }
        
        RETURN_IF_ERROR(StartCaptureThreads());
        
        fCard->CaptureStart();

        fCaptureActive = true;

        RefreshParameterWeb();

        return B_OK;    
}


status_t
DVBMediaNode::StopCapture()
{
        TRACE("DVBMediaNode::StopCapture\n");
        if (!fCaptureActive)
                return B_OK;

        StopCaptureThreads();
        
        fCard->CaptureStop();

        fCaptureActive = false;
        return B_OK;
}


status_t
DVBMediaNode::StartCaptureThreads()
{
        TRACE("DVBMediaNode::StartCaptureThreads\n");

        if (fCaptureThreadsActive)
                return B_OK;

        fTerminateThreads = false;

        fThreadIdCardReader = spawn_thread(_card_reader_thread_, "DVB card reader",
                120, this);
        fThreadIdMpegDemux = spawn_thread(_mpeg_demux_thread_, "DVB MPEG demux",
                110, this);
        fThreadIdEncAudio = spawn_thread(_enc_audio_thread_, "DVB audio streaming",
                110, this);
        fThreadIdEncVideo = spawn_thread(_enc_video_thread_, "DVB video streaming",
                110, this);
        fThreadIdMpegTS = spawn_thread(_mpeg_ts_thread_, "DVB MPEG TS streaming",
                110, this);
        fThreadIdRawAudio = spawn_thread(_raw_audio_thread_, "DVB audio decode",
                100, this);
        fThreadIdRawVideo = spawn_thread(_raw_video_thread_, "DVB video decode",
                85, this);
        resume_thread(fThreadIdCardReader);
        resume_thread(fThreadIdMpegDemux);
        resume_thread(fThreadIdEncAudio);
        resume_thread(fThreadIdEncVideo);
        resume_thread(fThreadIdMpegTS);
        resume_thread(fThreadIdRawAudio);
        resume_thread(fThreadIdRawVideo);
        
        fCaptureThreadsActive = true;
        return B_OK;
}


status_t
DVBMediaNode::StopCaptureThreads()
{
        TRACE("DVBMediaNode::StopCaptureThreads\n");
        
        if (!fCaptureThreadsActive)
                return B_OK;
        
        fTerminateThreads = true;

        fCardDataQueue->Terminate();
        fEncVideoQueue->Terminate();
        fEncAudioQueue->Terminate();
        fMpegTsQueue->Terminate();
        fRawVideoQueue->Terminate();
        fRawAudioQueue->Terminate();
        
        status_t dummy; // NULL as parameter does not work
        wait_for_thread(fThreadIdCardReader, &dummy);
        wait_for_thread(fThreadIdMpegDemux, &dummy);
        wait_for_thread(fThreadIdEncAudio, &dummy);
        wait_for_thread(fThreadIdEncVideo, &dummy);
        wait_for_thread(fThreadIdMpegTS, &dummy);
        wait_for_thread(fThreadIdRawAudio, &dummy);
        wait_for_thread(fThreadIdRawVideo, &dummy);

        fCardDataQueue->Restart();
        fEncVideoQueue->Restart();
        fEncAudioQueue->Restart();
        fMpegTsQueue->Restart();
        fRawVideoQueue->Restart();
        fRawAudioQueue->Restart();
        
        fCaptureThreadsActive = false;
        return B_OK;
}


int32
DVBMediaNode::_card_reader_thread_(void *arg)
{
        static_cast<DVBMediaNode *>(arg)->card_reader_thread();
        return 0;
}


int32
DVBMediaNode::_mpeg_demux_thread_(void *arg)
{
        static_cast<DVBMediaNode *>(arg)->mpeg_demux_thread();
        return 0;
}


int32
DVBMediaNode::_raw_audio_thread_(void *arg)
{
        static_cast<DVBMediaNode *>(arg)->raw_audio_thread();
        return 0;
}


int32
DVBMediaNode::_raw_video_thread_(void *arg)
{
        static_cast<DVBMediaNode *>(arg)->raw_video_thread();
        return 0;
}


int32
DVBMediaNode::_enc_audio_thread_(void *arg)
{
        static_cast<DVBMediaNode *>(arg)->enc_audio_thread();
        return 0;
}


int32
DVBMediaNode::_enc_video_thread_(void *arg)
{
        static_cast<DVBMediaNode *>(arg)->enc_video_thread();
        return 0;
}


int32
DVBMediaNode::_mpeg_ts_thread_(void *arg)
{
        static_cast<DVBMediaNode *>(arg)->mpeg_ts_thread();
        return 0;
}


void
DVBMediaNode::card_reader_thread()
{
        while (!fTerminateThreads) {
                void *data;
                size_t size;
                status_t err;
                bigtime_t end_time;
                err = fCard->Capture(&data, &size, &end_time);
                if (err != B_OK) {
                        TRACE("fCard->Capture failed, error %lx (%s)\n", err,
                                strerror(err));
                        continue;
                }

//              TRACE("captured %ld bytes\n", size);

                Packet *packet = new Packet(data, size, end_time);
                
                err = fCardDataQueue->Insert(packet);
                if (err != B_OK) {
                        delete packet;
                        TRACE("fCardDataQueue->Insert failed, error %lx\n", err);
                        continue;
                }
        }
}


void
DVBMediaNode::mpeg_demux_thread()
{
        while (!fTerminateThreads) {
                status_t err;
                Packet *packet;
                err = fCardDataQueue->Remove(&packet);
                if (err != B_OK) {
                        TRACE("fCardDataQueue->Remove failed, error %lx\n", err);
                        continue;
                }
                
                // packet->TimeStamp() is the end time of the capture
                fDemux->AddData(packet);
        }
}


void
DVBMediaNode::mpeg_ts_thread()
{
        while (!fTerminateThreads) {
                status_t err;
                Packet *packet;
                err = fMpegTsQueue->Remove(&packet);
                if (err != B_OK) {
                        TRACE("fMpegTsQueue->Remove failed, error %lx\n", err);
                        continue;
                }

//              TRACE("mpeg ts   packet, size %6ld, start_time %14Ld\n",
//                      packet->Size(), packet->TimeStamp());

#ifdef DUMP_MPEG_TS
                lock.Lock();
                write(fMpegTsFile, packet->Data(), packet->Size());
                lock.Unlock();
#endif

                delete packet;
        }
}


void
DVBMediaNode::enc_audio_thread()
{
        while (!fTerminateThreads) {
                status_t err;
                Packet *packet;
                err = fEncAudioQueue->Remove(&packet);
                if (err != B_OK) {
                        TRACE("fEncAudioQueue->Remove failed, error %lx\n", err);
                        continue;
                }
//              TRACE("enc audio packet, size %6ld, start_time %14Ld\n",
//                      packet->Size(), packet->TimeStamp());

#ifdef DUMP_AUDIO
                const uint8 *data;
                size_t size;
                if (B_OK != pes_extract(packet->Data(), packet->Size(), &data,
                                &size)) {
                        TRACE("audio pes_extract failed\n");
                        delete packet;
                        return;
                }
                lock.Lock();
                write(fAudioFile, data, size);
                lock.Unlock();
#endif

                if (!fOutputEnabledEncAudio) {
                        delete packet;
                        continue;
                }

                // send encoded audio buffer


                delete packet;
        }
}


void
DVBMediaNode::enc_video_thread()
{
        while (!fTerminateThreads) {
                status_t err;
                Packet *packet;
                err = fEncVideoQueue->Remove(&packet);
                if (err != B_OK) {
                        TRACE("fEncVideoQueue->Remove failed, error %lx\n", err);
                        continue;
                }

//              TRACE("enc video packet, size %6ld, start_time %14Ld\n",
//                      packet->Size(), packet->TimeStamp());


#ifdef DUMP_VIDEO
                int8 *data;
                size_t size;
                if (B_OK != pes_extract(packet->Data(), packet->Size(), &data,
                                &size)) {
                        TRACE("video pes_extract failed\n");
                        delete packet;
                        return;
                }
                lock.Lock();
                write(fVideoFile, data, size);
                lock.Unlock();
#endif

                if (!fOutputEnabledEncVideo) {
                        delete packet;
                        continue;
                }

                // send encoded video buffer

                delete packet;
        }
}


void
DVBMediaNode::raw_audio_thread()
{
        media_format format;
        status_t err;
        err = GetStreamFormat(fRawAudioQueue, &format);
        if (err) {
                printf("fAudioDecoder init error %s\n", strerror(err));
                return;
        }
        
        // create decoder interface

        fAudioDecoder = new MediaStreamDecoder(&_GetNextAudioChunk, this);
        
        err = fAudioDecoder->SetInputFormat(format);
        if (err) {
                printf("fAudioDecoder SetInputFormat error %s\n", strerror(err));
                return;
        }
        
        TRACE("requested audio decoder format: ");
        PrintFormat(fOutputRawAudio);
        
        media_format fmt = fOutputRawAudio.format;
        err = fAudioDecoder->SetOutputFormat(&fmt);
        if (err) {
                printf("fAudioDecoder SetOutputFormat error %s\n", strerror(err));
                return;
        }

        TRACE("final audio decoder format: ");
        PrintFormat(fmt);
                
        // change format of connection
        if (format_is_compatible(fmt, fOutputRawAudio.format)) {
                printf("audio formats are compatible\n");
                fOutputRawAudio.format = fmt;
        } else {
                printf("audio formats NOT compatible\n");
                lock.Lock();
                err = ChangeFormat(fOutputRawAudio.source,
                                                   fOutputRawAudio.destination,
                                                   &fmt);
                lock.Unlock();
                printf("format change result %lx (%s)\n", err, strerror(err));
                PrintFormat(fmt);
                fOutputRawAudio.format = fmt;
                if (err)
                        return;
        }
        
        // decode data and send buffers
        
        delete fBufferGroupRawAudio;
        fBufferGroupRawAudio = new BBufferGroup(
                fOutputRawAudio.format.u.raw_audio.buffer_size * 3, 25);

        while (!fTerminateThreads) {
                int64 frameCount;
                media_header mh;
                
                if (!fOutputEnabledRawAudio) {
                        fRawAudioQueue->Flush(40000);
                        continue;
                }

                BBuffer* buf;
                buf = fBufferGroupRawAudio->RequestBuffer(
                        fOutputRawAudio.format.u.raw_audio.buffer_size,
                        AUDIO_BUFFER_REQUEST_TIMEOUT);
                if (!buf) {
                        TRACE("audio: request buffer timout\n");
                        continue;
                }

                err = fAudioDecoder->Decode(buf->Data(), &frameCount, &mh, NULL);
                if (err) {
                        buf->Recycle();
                        printf("fAudioDecoder Decode error %s\n", strerror(err));
                        continue;
                }

#ifdef DUMP_RAW_AUDIO
                lock.Lock();
                write(fRawAudioFile, buf->Data(), mh.size_used);
                lock.Unlock();
#endif

                if (fOutputRawAudio.format.u.raw_audio.buffer_size != mh.size_used
                        || int(fOutputRawAudio.format.u.raw_audio.frame_rate)
                                != mh.u.raw_audio.frame_rate
                        || fOutputRawAudio.format.u.raw_audio.channel_count
                                != mh.u.raw_audio.channel_count) {
                        TRACE("audio: decode format change: changed buffer_size from %ld"
                                " to %ld\n", fOutputRawAudio.format.u.raw_audio.buffer_size,
                                mh.size_used);
                        TRACE("audio: decode format change: changed channel_count from %ld"
                                " to %ld\n", fOutputRawAudio.format.u.raw_audio.channel_count,
                                mh.u.raw_audio.channel_count);
                        TRACE("audio: decode format change: changed frame_rate from %.0f"
                                " to %.0f\n", fOutputRawAudio.format.u.raw_audio.frame_rate,
                                mh.u.raw_audio.frame_rate);
                        fOutputRawAudio.format.u.raw_audio.buffer_size = mh.size_used;
                        fOutputRawAudio.format.u.raw_audio.frame_rate
                                = mh.u.raw_audio.frame_rate;
                        fOutputRawAudio.format.u.raw_audio.channel_count
                                = mh.u.raw_audio.channel_count;
                        lock.Lock();
                        err = ChangeFormat(fOutputRawAudio.source,
                                fOutputRawAudio.destination, &fOutputRawAudio.format);
                        lock.Unlock();
                        printf("format change result %lx (%s)\n", err, strerror(err));
                        PrintFormat(fOutputRawAudio.format);
                        if (err) {
                                buf->Recycle();
                                return; // we are dead!
                        }
                }
        
                bigtime_t ts_perf_time;
                bigtime_t ts_sys_time;
                bigtime_t ts_offset;
                bigtime_t aud_time;
                bigtime_t start_time;

                // calculate start time of audio data

                fDemux->TimesourceInfo(&ts_perf_time, &ts_sys_time);
                ts_offset = ts_sys_time - ts_perf_time;
                aud_time = mh.start_time;       // measured in PCR time base
                start_time = TimeSource()->PerformanceTimeFor(aud_time + ts_offset);

                // calculate delay and wait

                bigtime_t delay;
                delay = start_time - TimeSource()->Now();
                TRACE_TIMING("audio delay is %lld\n", delay);
                if (delay < -AUDIO_MAX_LATE) {
                        printf("audio: decoded packet is %lldms too late, dropped\n",
                                -delay / 1000);
                        buf->Recycle();
                        continue;
                }
                if (delay < 0)
//                      printf("audio: decoded packet is %lldms too late\n", -delay / 1000);

                if (delay > AUDIO_MAX_EARLY) {
                        printf("audio: decoded packet is %lldms too early, dropped\n",
                                delay / 1000);
                        buf->Recycle();
                        continue;
                }
                if (delay > 0)
//                      printf("audio: decoded packet is %lldms too early\n", delay / 1000);

                delay -= PROCESSING_LATENCY;
                if (delay > 0) {
                        if (acquire_sem_etc(fAudioDelaySem, 1, B_RELATIVE_TIMEOUT, delay)
                                != B_TIMED_OUT) {
                                printf("audio: delay sem not timed out, dropped packet\n");
                                buf->Recycle();
                                continue;
                        }
                }

                TRACE_TIMING("audio playback delay %lld\n",
                        start_time - TimeSource()->Now());

                media_header* hdr;
                hdr = buf->Header();
                hdr->type = B_MEDIA_RAW_AUDIO;
                hdr->size_used = mh.size_used;
                hdr->time_source = TimeSource()->ID();
                hdr->start_time = start_time;
                lock.Lock();
                if (SendBuffer(buf, fOutputRawAudio.source,
                        fOutputRawAudio.destination) != B_OK) {
                        TRACE("audio: sending buffer failed\n");
                        buf->Recycle();
                } 
                lock.Unlock();
        }

        delete fCurrentAudioPacket;
        fCurrentAudioPacket = 0;
}


void
DVBMediaNode::raw_video_thread()
{
        media_format format;
        status_t err;
        err = GetStreamFormat(fRawVideoQueue, &format);
        if (err) {
                printf("fVideoDecoder init error %s\n", strerror(err));
                return;
        }
        
        // create decoder interface

        fVideoDecoder = new MediaStreamDecoder(&_GetNextVideoChunk, this);
        
        err = fVideoDecoder->SetInputFormat(format);
        if (err) {
                printf("fVideoDecoder SetInputFormat error %s\n", strerror(err));
                return;
        }
        
        TRACE("requested video decoder format: ");
        PrintFormat(fOutputRawVideo);
        
        media_format fmt = fOutputRawVideo.format;
        err = fVideoDecoder->SetOutputFormat(&fmt);
        if (err) {
                printf("fVideoDecoder SetOutputFormat error %s\n", strerror(err));
                return;
        }

        TRACE("final video decoder format: ");
        PrintFormat(fmt);
                
        // change format of connection
        if (format_is_compatible(fmt, fOutputRawVideo.format)) {
                printf("video formats are compatible\n");
                fOutputRawVideo.format = fmt;
        } else {
                printf("video formats NOT compatible\n");
                lock.Lock();
                err = ChangeFormat(fOutputRawVideo.source,
                                                   fOutputRawVideo.destination,
                                                   &fmt);
                lock.Unlock();
                printf("format change result %lx (%s)\n", err, strerror(err));
                PrintFormat(fmt);
                fOutputRawVideo.format = fmt;
                if (err) {
                        printf("video format change failed\n");
                        return;
                }
        }
        
        // decode data and send buffers

        uint32 video_buffer_size_max = 720 * 576 * 4;
        uint32 video_buffer_size
                = fOutputRawVideo.format.u.raw_video.display.line_count
                        * fOutputRawVideo.format.u.raw_video.display.bytes_per_row;
        
        delete fBufferGroupRawVideo;
        fBufferGroupRawVideo = new BBufferGroup(video_buffer_size_max, 4);

        while (!fTerminateThreads) {
                int64 frameCount;
                media_header mh;

                if (!fOutputEnabledRawVideo) {
                        fRawVideoQueue->Flush(40000);
                        continue;
                }

                // fetch a new buffer (always of maximum size as the stream may change)
                BBuffer* buf;
                buf = fBufferGroupRawVideo->RequestBuffer(video_buffer_size_max,
                        VIDEO_BUFFER_REQUEST_TIMEOUT);
                if (!buf) {
                        TRACE("video: request buffer timout\n");
                        continue;
                }
                
                // decode one video frame into buffer
                err = fVideoDecoder->Decode(buf->Data(), &frameCount, &mh, NULL);
                if (err) {
                        buf->Recycle();
                        printf("fVideoDecoder Decode error %s\n", strerror(err));
                        continue;
                }

                // check if the format of the stream has changed
                if (mh.u.raw_video.display_line_width
                        != fOutputRawVideo.format.u.raw_video.display.line_width
                        || mh.u.raw_video.display_line_count
                                != fOutputRawVideo.format.u.raw_video.display.line_count
                        || mh.u.raw_video.bytes_per_row
                                != fOutputRawVideo.format.u.raw_video.display.bytes_per_row
                        || mh.u.raw_video.pixel_width_aspect
                                != fOutputRawVideo.format.u.raw_video.pixel_width_aspect
                        || mh.u.raw_video.pixel_height_aspect
                                != fOutputRawVideo.format.u.raw_video.pixel_height_aspect
                        || mh.size_used != video_buffer_size) {
                        printf("video format changed:\n");
                        printf(" line_width %ld => %ld\n",
                                fOutputRawVideo.format.u.raw_video.display.line_width,
                                mh.u.raw_video.display_line_width);
                        printf(" line_count %ld => %ld\n",
                                fOutputRawVideo.format.u.raw_video.display.line_count,
                                mh.u.raw_video.display_line_count);
                        printf(" bytes_per_row %ld => %ld\n",
                                fOutputRawVideo.format.u.raw_video.display.bytes_per_row,
                                mh.u.raw_video.bytes_per_row);
                        printf(" pixel_width_aspect %d => %d\n",
                                fOutputRawVideo.format.u.raw_video.pixel_width_aspect,
                                mh.u.raw_video.pixel_width_aspect);
                        printf(" pixel_height_aspect %d => %d\n",
                                fOutputRawVideo.format.u.raw_video.pixel_height_aspect,
                                mh.u.raw_video.pixel_height_aspect);
                        printf(" video_buffer_size %ld => %ld\n", video_buffer_size,
                                mh.size_used);

                        // recalculate video buffer size
                        video_buffer_size
                                = fOutputRawVideo.format.u.raw_video.display.line_count
                                        * fOutputRawVideo.format.u.raw_video.display.bytes_per_row;

                        // perform a video format change
                        fOutputRawVideo.format.u.raw_video.display.line_width
                                = mh.u.raw_video.display_line_width;
                        fOutputRawVideo.format.u.raw_video.display.line_count
                                = mh.u.raw_video.display_line_count;
                        fOutputRawVideo.format.u.raw_video.display.bytes_per_row
                                = mh.u.raw_video.bytes_per_row;
                        fOutputRawVideo.format.u.raw_video.pixel_width_aspect
                                = mh.u.raw_video.pixel_width_aspect;
                        fOutputRawVideo.format.u.raw_video.pixel_height_aspect
                                = mh.u.raw_video.pixel_height_aspect;
                        fOutputRawVideo.format.u.raw_video.last_active
                                = mh.u.raw_video.display_line_count - 1;
                        lock.Lock();
                        err = ChangeFormat(fOutputRawVideo.source,
                                fOutputRawVideo.destination, &fOutputRawVideo.format);
                        lock.Unlock();
                        printf("format change result %lx (%s)\n", err, strerror(err));
                        PrintFormat(fOutputRawVideo.format);
                        if (err) {
                                buf->Recycle();
                                printf("video format change failed\n");
                                return; // we are dead
                        }
                }
                
                // calculate start time for video
                bigtime_t ts_perf_time;
                bigtime_t ts_sys_time;
                bigtime_t ts_offset;
                bigtime_t pic_time;
                bigtime_t start_time;

                fDemux->TimesourceInfo(&ts_perf_time, &ts_sys_time);
                ts_offset = ts_sys_time - ts_perf_time;
                pic_time = mh.start_time;       // measured in PCR time base
                start_time = TimeSource()->PerformanceTimeFor(pic_time + ts_offset);

                // calculate delay and wait

                bigtime_t delay;
                delay = start_time - TimeSource()->Now();
                TRACE_TIMING("video delay %lld\n", delay);
                if (delay < -VIDEO_MAX_LATE) {
                        printf("video: decoded packet is %lldms too late, dropped\n",
                                -delay / 1000);
                        buf->Recycle();
                        continue;
                }
                if (delay > VIDEO_MAX_EARLY) {
                        printf("video: decoded packet is %lldms too early, dropped\n",
                                delay / 1000);
                        buf->Recycle();
                        continue;
                }
                delay -= PROCESSING_LATENCY;
                if (delay > 0) {
                        if (acquire_sem_etc(fVideoDelaySem, 1, B_RELATIVE_TIMEOUT, delay)
                                != B_TIMED_OUT) {
                                printf("video: delay sem not timed out, dropped packet\n");
                                buf->Recycle();
                                continue;
                        }
                }

                TRACE_TIMING("video playback delay %lld\n", start_time 
                        - TimeSource()->Now());

                media_header* hdr;
                hdr = buf->Header();
                hdr->type = B_MEDIA_RAW_VIDEO;
                hdr->size_used = video_buffer_size;
                hdr->time_source = TimeSource()->ID();
                hdr->start_time = start_time;
                lock.Lock();
                if (SendBuffer(buf, fOutputRawVideo.source,
                                fOutputRawVideo.destination) != B_OK) {
                        TRACE("video: sending buffer failed\n");
                        buf->Recycle();
                } 
                lock.Unlock();
        }

        delete fCurrentVideoPacket;
        fCurrentVideoPacket = 0;
}


inline status_t
DVBMediaNode::GetNextVideoChunk(const void **chunkData, size_t *chunkLen,
        media_header *mh)
{
//      TRACE("DVBMediaNode::GetNextVideoChunk\n");

        delete fCurrentVideoPacket;
        
        status_t err;
        err = fRawVideoQueue->Remove(&fCurrentVideoPacket);
        if (err != B_OK) {
                TRACE("fRawVideoQueue->Remove failed, error %lx\n", err);
                fCurrentVideoPacket = 0;
                return B_ERROR;
        }

        const uint8 *data;
        size_t size;
                        
        if (B_OK != pes_extract(fCurrentVideoPacket->Data(),
                        fCurrentVideoPacket->Size(), &data, &size)) {
                TRACE("video pes_extract failed\n");
                return B_ERROR;
        }
        
        *chunkData = data;
        *chunkLen = size;
        // measured in PCR time base
        mh->start_time = fCurrentVideoPacket->TimeStamp();

        return B_OK;
}


inline status_t
DVBMediaNode::GetNextAudioChunk(const void **chunkData, size_t *chunkLen,
        media_header *mh)
{
//      TRACE("DVBMediaNode::GetNextAudioChunk\n");

        delete fCurrentAudioPacket;
        
        status_t err;
        err = fRawAudioQueue->Remove(&fCurrentAudioPacket);
        if (err != B_OK) {
                TRACE("fRawAudioQueue->Remove failed, error %lx\n", err);
                fCurrentAudioPacket = 0;
                return B_ERROR;
        }

        const uint8 *data;
        size_t size;
                        
        if (B_OK != pes_extract(fCurrentAudioPacket->Data(),
                        fCurrentAudioPacket->Size(), &data, &size)) {
                TRACE("audio pes_extract failed\n");
                return B_ERROR;
        }
        
        *chunkData = data;
        *chunkLen = size;
        // measured in PCR time base
        mh->start_time = fCurrentAudioPacket->TimeStamp();

//      printf("GetNextAudioChunk: done start_time %lld\n", mh->start_time);

        return B_OK;
}


status_t
DVBMediaNode::_GetNextVideoChunk(const void **chunkData, size_t *chunkLen,
        media_header *mh, void *cookie)
{
        return static_cast<DVBMediaNode *>(cookie)->GetNextVideoChunk(chunkData,
                chunkLen, mh);
}


status_t
DVBMediaNode::_GetNextAudioChunk(const void **chunkData, size_t *chunkLen,
        media_header *mh, void *cookie)
{
        return static_cast<DVBMediaNode *>(cookie)->GetNextAudioChunk(chunkData,
                chunkLen, mh);
}


status_t
DVBMediaNode::GetStreamFormat(PacketQueue *queue, media_format *format)
{
        status_t status;
        Packet *packet;
        const uint8 *data;
        size_t size;
        int stream_id;
        
        // get copy of the first packet from queue, and determine format
        status = queue->Peek(&packet);
        if (status != B_OK) {
                TRACE("queue->Peek failed, error %lx\n", status);
                return status;
        }
        status = pes_extract(packet->Data(), packet->Size(), &data, &size);
        if (status != B_OK) {
                TRACE("pes_extract failed\n");
                goto done;
        }
        status = pes_stream_id(packet->Data(), packet->Size(), &stream_id);
        if (status != B_OK) {
                TRACE("pes_stream_id failed\n");
                goto done;
        }
        status = GetHeaderFormat(format, data, size, stream_id);
        if (status != B_OK) {
                TRACE("GetHeaderFormat failed, error %lx\n", status);
                goto done;
        }

done:
        delete packet;
        return status;
}


enum {
        ID_STATE        = 11,
        ID_REGION       = 12,
        ID_CHANNEL      = 13,
        ID_AUDIO        = 14,
};


void
DVBMediaNode::RefreshParameterWeb()
{
        TRACE("DVBMediaNode::RefreshParameterWeb enter\n");
        fWeb = CreateParameterWeb();
        SetParameterWeb(fWeb);
        TRACE("DVBMediaNode::RefreshParameterWeb finished\n");
}


void
DVBMediaNode::SetAboutInfo(BParameterGroup *about)
{
        about->MakeNullParameter(0, B_MEDIA_NO_TYPE, "DVB media_addon info:",
                B_GENERIC);
        about->MakeNullParameter(0, B_MEDIA_NO_TYPE, "Version " VERSION,
                B_GENERIC);
        about->MakeNullParameter(0, B_MEDIA_NO_TYPE, "Revision " REVISION,
                B_GENERIC);
        about->MakeNullParameter(0, B_MEDIA_NO_TYPE, "Build " BUILD, B_GENERIC);

        about->MakeNullParameter(0, B_MEDIA_NO_TYPE, "", B_GENERIC);
        about->MakeNullParameter(0, B_MEDIA_NO_TYPE, "Driver info:", B_GENERIC);

        dvb_type_t type;
        char name[200];
        char info[200];
        
        fCard->GetCardType(&type);
        fCard->GetCardInfo(name, sizeof(name), info, sizeof(info));

        about->MakeNullParameter(0, B_MEDIA_NO_TYPE, name, B_GENERIC);
        about->MakeNullParameter(0, B_MEDIA_NO_TYPE, info, B_GENERIC);
}


BParameterWeb *
DVBMediaNode::CreateParameterWeb()
{
        /* Set up the parameter web */
        BParameterWeb *web = new BParameterWeb();

        char n[200], i[200];
        fCard->GetCardInfo(n, sizeof(n), i, sizeof(i));
        
        BString name;
        name << Name() << " - " << i;
        
        BParameterGroup *main = web->MakeGroup(name.String());
        
        BParameterGroup *ctrl = main->MakeGroup("Channel Selection");
        ctrl->MakeNullParameter(0, B_MEDIA_NO_TYPE, ctrl->Name(), B_GENERIC);

        BParameterGroup *pref = main->MakeGroup("Preferences");
        pref->MakeNullParameter(0, B_MEDIA_NO_TYPE, pref->Name(), B_GENERIC);

        BDiscreteParameter *state = pref->MakeDiscreteParameter(
                        ID_STATE, B_MEDIA_RAW_VIDEO, "State", B_GENERIC);

        BDiscreteParameter *region = pref->MakeDiscreteParameter(
                        ID_REGION, B_MEDIA_RAW_VIDEO, "Region", B_GENERIC);

        BDiscreteParameter *chan = ctrl->MakeDiscreteParameter(
                        ID_CHANNEL, B_MEDIA_RAW_VIDEO, "Channel",
                        /* B_TUNER_CHANNEL */ B_GENERIC);

        BDiscreteParameter *aud = ctrl->MakeDiscreteParameter(
                        ID_AUDIO, B_MEDIA_RAW_VIDEO, "Audio", B_GENERIC);

        AddStateItems(state);
        AddRegionItems(region);
        AddChannelItems(chan);
        AddAudioItems(aud);


        if (!fTuningSuccess || !fCaptureActive) {
                BParameterGroup *info = main->MakeGroup("Info");
                info->MakeNullParameter(0, B_MEDIA_NO_TYPE, info->Name(), B_GENERIC);
                BParameterGroup *about = main->MakeGroup("About");
                about->MakeNullParameter(0, B_MEDIA_NO_TYPE, about->Name(), B_GENERIC);
                SetAboutInfo(about);
//              info->MakeNullParameter(0, B_MEDIA_NO_TYPE,
//                      fCaptureActive ? "Tuning failed" : "Node stopped", B_GENERIC);
                info->MakeNullParameter(0, B_MEDIA_NO_TYPE, "Node is stopped",
                        B_GENERIC);
                info->MakeNullParameter(0, B_MEDIA_NO_TYPE, "or tuning failed.",
                        B_GENERIC);
                return web;
        }

        BParameterGroup *info1 = main->MakeGroup("Info");
        info1->MakeNullParameter(0, B_MEDIA_NO_TYPE, info1->Name(), B_GENERIC);
        BParameterGroup *info2 = main->MakeGroup("Info");
        info2->MakeNullParameter(0, B_MEDIA_NO_TYPE, info2->Name(), B_GENERIC);
        BParameterGroup *about = main->MakeGroup("About");
        about->MakeNullParameter(0, B_MEDIA_NO_TYPE, about->Name(), B_GENERIC);
        SetAboutInfo(about);
        
        BString sInterfaceType = "Interface Type: ";
        BString sFrequency = "Frequency: ";
        BString sAudioPid = "Audio PID: ";
        BString sVideoPid = "Video PID: ";
        BString sPcrPid = "PCR PID: ";
        BString sInversion = "Inversion: ";
        BString sBandwidth = "Bandwith: ";
        BString sModulation = "Modulation: ";
        BString sHierarchy = "Hierarchy: ";
        BString sCodeRateHP = "Code Rate HP: ";
        BString sCodeRateLP = "Code Rate LP: ";
        BString sTransmissionMode = "Transmission Mode: ";
        BString sGuardInterval = "Guard Interval: ";
        
        BString sSymbolRate = "Symbol Rate: ";
        BString sPolarity = "Polarity: ";
        BString sAgcInversion = "AGC Inversion: ";

        switch (fInterfaceType) {
                case DVB_TYPE_DVB_C:
                        sInterfaceType << "DVB-C";
                        break;

                case DVB_TYPE_DVB_H:
                        sInterfaceType << "DVB-H";
                        break;

                case DVB_TYPE_DVB_S:
                        sInterfaceType << "DVB-S";
                        break;

                case DVB_TYPE_DVB_T:
                        sInterfaceType << "DVB-T";
                        break;

                default:
                        sInterfaceType << "unknown";
                        break;
        }

        sAudioPid << fAudioPid;
        sVideoPid << fVideoPid;
        sPcrPid << fPcrPid;
        
        if (fInterfaceType == DVB_TYPE_DVB_T) {

                sFrequency << fTuningParam.u.dvb_t.frequency / 1000000 << " MHz";

                switch (fTuningParam.u.dvb_t.inversion) {
                        case DVB_INVERSION_AUTO:
                                sInversion << "auto";
                                break;

                        case DVB_INVERSION_ON:
                                sInversion << "on";
                                break;

                        case DVB_INVERSION_OFF:
                                sInversion << "off";
                                break;

                        default:
                                sInversion << "unknown";
                                break;
                }

                switch (fTuningParam.u.dvb_t.bandwidth) {
                        case DVB_BANDWIDTH_AUTO:
                                sBandwidth << "auto";
                                break;

                        case DVB_BANDWIDTH_6_MHZ:
                                sBandwidth << "6 MHz";
                                break;

                        case DVB_BANDWIDTH_7_MHZ:
                                sBandwidth << "7 MHz";
                                break;

                        case DVB_BANDWIDTH_8_MHZ:
                                sBandwidth << "8 MHz";
                                break;

                        default:
                                sBandwidth << "unknown";
                                break;
                }

                switch (fTuningParam.u.dvb_t.modulation) {
                        case DVB_MODULATION_AUTO:
                                sModulation << "auto";
                                break;

                        case DVB_MODULATION_QPSK:
                                sModulation << "QPSK";
                                break;

                        case DVB_MODULATION_16_QAM:
                                sModulation << "16 QAM";
                                break;

                        case DVB_MODULATION_32_QAM:
                                sModulation << "32 QAM";
                                break;

                        case DVB_MODULATION_64_QAM:
                                sModulation << "64 QAM";
                                break;

                        case DVB_MODULATION_128_QAM:
                                sModulation << "128 QAM";
                                break;

                        case DVB_MODULATION_256_QAM:
                                sModulation << "256 QAM";
                                break;

                        default:
                                sModulation << "unknown";
                                break;
                }
                
                switch (fTuningParam.u.dvb_t.hierarchy) {
                        case DVB_HIERARCHY_AUTO:
                                sHierarchy << "auto";
                                break;

                        case DVB_HIERARCHY_NONE:
                                sHierarchy << "none";
                                break;

                        case DVB_HIERARCHY_1:
                                sHierarchy << "1";
                                break;

                        case DVB_HIERARCHY_2:
                                sHierarchy << "2";
                                break;

                        case DVB_HIERARCHY_4:
                                sHierarchy << "4";
                                break;

                        default:
                                sHierarchy << "unknown";
                                break;
                }
                
                switch (fTuningParam.u.dvb_t.code_rate_hp) {
                        case DVB_FEC_AUTO:
                                sCodeRateHP << "auto";
                                break;

                        case DVB_FEC_NONE:
                                sCodeRateHP << "none";
                                break;

                        case DVB_FEC_1_2:
                                sCodeRateHP << "FEC 1/2";
                                break;

                        case DVB_FEC_2_3:
                                sCodeRateHP << "FEC 2/3";
                                break;

                        case DVB_FEC_3_4:
                                sCodeRateHP << "FEC 3/4";
                                break;

                        case DVB_FEC_4_5:
                                sCodeRateHP << "FEC 4/5";
                                break;

                        case DVB_FEC_5_6:
                                sCodeRateHP << "FEC 5/6";
                                break;

                        case DVB_FEC_6_7:
                                sCodeRateHP << "FEC 6/7";
                                break;

                        case DVB_FEC_7_8:
                                sCodeRateHP << "FEC 7/8";
                                break;

                        case DVB_FEC_8_9:
                                sCodeRateHP << "FEC 8/9";
                                break;

                        default:
                                sCodeRateHP << "unknown";
                                break;
                }

                switch (fTuningParam.u.dvb_t.code_rate_lp) {
                        case DVB_FEC_AUTO:
                                sCodeRateLP << "auto";
                                break;

                        case DVB_FEC_NONE:
                                sCodeRateLP << "none";
                                break;

                        case DVB_FEC_1_2:
                                sCodeRateLP << "FEC 1/2";
                                break;

                        case DVB_FEC_2_3:
                                sCodeRateLP << "FEC 2/3";
                                break;

                        case DVB_FEC_3_4:
                                sCodeRateLP << "FEC 3/4";
                                break;

                        case DVB_FEC_4_5:
                                sCodeRateLP << "FEC 4/5";
                                break;

                        case DVB_FEC_5_6:
                                sCodeRateLP << "FEC 5/6";
                                break;

                        case DVB_FEC_6_7:
                                sCodeRateLP << "FEC 6/7";
                                break;

                        case DVB_FEC_7_8:
                                sCodeRateLP << "FEC 7/8";
                                break;

                        case DVB_FEC_8_9:
                                sCodeRateLP << "FEC 8/9";
                                break;

                        default:
                                sCodeRateLP << "unknown";
                                break;
                }

                switch (fTuningParam.u.dvb_t.transmission_mode) {
                        case DVB_TRANSMISSION_MODE_AUTO:
                                sTransmissionMode << "auto";
                                break;

                        case DVB_TRANSMISSION_MODE_2K:
                                sTransmissionMode << "2K";
                                break;

                        case DVB_TRANSMISSION_MODE_4K:
                                sTransmissionMode << "4K";
                                break;

                        case DVB_TRANSMISSION_MODE_8K:
                                sTransmissionMode << "8K";
                                break;

                        default:
                                sTransmissionMode << "unknown";
                                break;
                }

                switch (fTuningParam.u.dvb_t.guard_interval) {
                        case DVB_GUARD_INTERVAL_AUTO:
                                sGuardInterval << "auto";
                                break;

                        case DVB_GUARD_INTERVAL_1_4:
                                sGuardInterval << "1/4";
                                break;

                        case DVB_GUARD_INTERVAL_1_8:
                                sGuardInterval << "1/8";
                                break;

                        case DVB_GUARD_INTERVAL_1_16:
                                sGuardInterval << "1/16";
                                break;

                        case DVB_GUARD_INTERVAL_1_32:
                                sGuardInterval << "1/32";
                                break;

                        default:
                                sGuardInterval << "unknown";
                                break;
                }

                info1->MakeNullParameter(0, B_MEDIA_NO_TYPE, sInterfaceType.String(),
                        B_GENERIC);
                info1->MakeNullParameter(0, B_MEDIA_NO_TYPE, sFrequency.String(),
                        B_GENERIC);
                info1->MakeNullParameter(0, B_MEDIA_NO_TYPE, sBandwidth.String(),
                        B_GENERIC);
                info1->MakeNullParameter(0, B_MEDIA_NO_TYPE, sVideoPid.String(),
                        B_GENERIC);
                info1->MakeNullParameter(0, B_MEDIA_NO_TYPE, sAudioPid.String(),
                        B_GENERIC);
                info1->MakeNullParameter(0, B_MEDIA_NO_TYPE, sPcrPid.String(),
                        B_GENERIC);

                info2->MakeNullParameter(0, B_MEDIA_NO_TYPE, sModulation.String(),
                        B_GENERIC);
                info2->MakeNullParameter(0, B_MEDIA_NO_TYPE,
                        sTransmissionMode.String(), B_GENERIC);
                info2->MakeNullParameter(0, B_MEDIA_NO_TYPE, sGuardInterval.String(),
                        B_GENERIC);
                info2->MakeNullParameter(0, B_MEDIA_NO_TYPE, sCodeRateHP.String(),
                        B_GENERIC);
                info2->MakeNullParameter(0, B_MEDIA_NO_TYPE, sCodeRateLP.String(),
                        B_GENERIC);
                info2->MakeNullParameter(0, B_MEDIA_NO_TYPE, sInversion.String(),
                        B_GENERIC);
                info2->MakeNullParameter(0, B_MEDIA_NO_TYPE, sHierarchy.String(),
                        B_GENERIC);
        }

        if (fInterfaceType == DVB_TYPE_DVB_S) {

                sFrequency << fTuningParam.u.dvb_s.frequency / 1000000 << " MHz";
                sSymbolRate << fTuningParam.u.dvb_s.symbolrate;

                switch (fTuningParam.u.dvb_s.inversion) {
                        case DVB_INVERSION_AUTO:
                                sInversion << "auto";
                                break;

                        case DVB_INVERSION_ON:
                                sInversion << "on";
                                break;

                        case DVB_INVERSION_OFF:
                                sInversion << "off";
                                break;

                        default:
                                sInversion << "unknown";
                                break;
                }
        
                switch (fTuningParam.u.dvb_s.polarity) {
                        case DVB_POLARITY_VERTICAL:
                                sPolarity << "vertical";
                                break;

                        case DVB_POLARITY_HORIZONTAL:
                                sPolarity << "horizontal";
                                break;

                        default:
                                sPolarity << "unknown";
                                break;
                }

                info1->MakeNullParameter(0, B_MEDIA_NO_TYPE, sInterfaceType.String(),
                        B_GENERIC);
                info1->MakeNullParameter(0, B_MEDIA_NO_TYPE, sVideoPid.String(),
                        B_GENERIC);
                info1->MakeNullParameter(0, B_MEDIA_NO_TYPE, sAudioPid.String(),
                        B_GENERIC);
                info1->MakeNullParameter(0, B_MEDIA_NO_TYPE, sPcrPid.String(),
                        B_GENERIC);

                info2->MakeNullParameter(0, B_MEDIA_NO_TYPE, sFrequency.String(),
                        B_GENERIC);
                info2->MakeNullParameter(0, B_MEDIA_NO_TYPE, sPolarity.String(),
                        B_GENERIC);
                info2->MakeNullParameter(0, B_MEDIA_NO_TYPE, sSymbolRate.String(),
                        B_GENERIC);
                info2->MakeNullParameter(0, B_MEDIA_NO_TYPE, sInversion.String(),
                        B_GENERIC);
                info2->MakeNullParameter(0, B_MEDIA_NO_TYPE, sAgcInversion.String(),
                        B_GENERIC);
        }

        return web;
}


void
DVBMediaNode::LoadSettings()
{
        TRACE("DVBMediaNode::LoadSettings\n");
        RefreshStateList();
        fSelectedState = 0;
        RefreshRegionList();
        fSelectedRegion = 0;
        RefreshChannelList();
        fSelectedChannel = 0;
        RefreshAudioList();
        fSelectedAudio = 0;
}


void
DVBMediaNode::RefreshStateList()
{
        TRACE("DVBMediaNode::RefreshStateList\n");

        fStateList->MakeEmpty();
        fSelectedState = -1;

        const char *dir;
        switch (fInterfaceType) {
                case DVB_TYPE_DVB_C:
                        dir = "/boot/home/config/settings/Media/dvb/dvb-c channels";
                        break;

                case DVB_TYPE_DVB_H:
                        dir = "/boot/home/config/settings/Media/dvb/dvb-h channels";
                        break;

                case DVB_TYPE_DVB_S:
                        dir = "/boot/home/config/settings/Media/dvb/dvb-s channels";
                        break;

                case DVB_TYPE_DVB_T:
                        dir = "/boot/home/config/settings/Media/dvb/dvb-t channels";
                        break;

                default:
                        printf("DVBMediaNode::RefreshStateList unknown interface type\n");
                        return;
        }
        
        TRACE("loading channel lists from dir = %s\n", dir);
        
        BDirectory d(dir);
        BEntry e;
        BPath p;
        while (B_OK == d.GetNextEntry(&e, false)) {
                if (B_OK != e.GetPath(&p))
                        continue;
                fStateList->AddItem(p.Path());
        }
        
        if (fStateList->ItemAt(0))
                fSelectedState = 0;
}


void
DVBMediaNode::RefreshRegionList()
{
        TRACE("DVBMediaNode::RefreshRegionList\n");

        fRegionList->MakeEmpty();
        fSelectedRegion = -1;

        const char *dir = fStateList->ItemAt(fSelectedState);
        if (!dir)
                return;

        BDirectory d(dir);
        BEntry e;
        BPath p;
        while (B_OK == d.GetNextEntry(&e, false)) {
                if (B_OK != e.GetPath(&p))
                        continue;
                fRegionList->AddItem(p.Path());
        }

        if (fRegionList->ItemAt(0))
                fSelectedRegion = 0;
}


void
DVBMediaNode::RefreshChannelList()
{
        TRACE("DVBMediaNode::RefreshChannelList\n");

        fChannelList->MakeEmpty();
        fSelectedChannel = -1;

        const char *path = fRegionList->ItemAt(fSelectedRegion);
        if (!path)
                return;
                
        TRACE("opening channel list file = %s\n", path);
        
        FILE *f = fopen(path, "r");
        if (!f)
                return;

        char line[1024];
        while (fgets(line, sizeof(line), f)) {
                if (line[0] == ':') // skip comments
                        continue;
                if (strchr(line, ':') == NULL)  // skip empty lines
                        continue;
                fChannelList->AddItem(line);
        }

        fclose(f);      

        if (fChannelList->ItemAt(0))
                fSelectedChannel = 0;
}


void
DVBMediaNode::RefreshAudioList()
{
        TRACE("DVBMediaNode::RefreshAudioList\n");

        fAudioList->MakeEmpty();        
        fSelectedAudio = -1;
        
        fAudioList->AddItem("default"); // XXX test
        
        if (fAudioList->ItemAt(0))
                fSelectedAudio = 0;
}


void
DVBMediaNode::AddStateItems(BDiscreteParameter *param)
{
        TRACE("DVBMediaNode::AddStateItems\n");
        
        const char *str;
        for (int i = 0; (str = fStateList->ItemAt(i)); i++) {
                str = strrchr(str, '/');
                if (!str)
                        continue;
                str++;
                param->AddItem(i, str);
        }
        if (param->CountItems() == 0)
                param->AddItem(-1, "none");
}


void
DVBMediaNode::AddRegionItems(BDiscreteParameter *param)
{
        TRACE("DVBMediaNode::AddRegionItems\n");

        const char *str;
        for (int i = 0; (str = fRegionList->ItemAt(i)); i++) {
                str = strrchr(str, '/');
                if (!str)
                        continue;
                str++;
                param->AddItem(i, str);
        }
        if (param->CountItems() == 0)
                param->AddItem(-1, "none");
}


void
DVBMediaNode::AddChannelItems(BDiscreteParameter *param)
{
        TRACE("DVBMediaNode::AddChannelItems\n");

        const char *str;
        for (int i = 0; (str = fChannelList->ItemAt(i)); i++) {
                char name[256];
//              sscanf(str, "%s:", name);
                sscanf(str, "%[^:]", name);
                param->AddItem(i, name);

        }
        if (param->CountItems() == 0)
                param->AddItem(-1, "none");
}


void
DVBMediaNode::AddAudioItems(BDiscreteParameter *param)
{
        TRACE("DVBMediaNode::AddAudioItems\n");

        if (param->CountItems() == 0)
                param->AddItem(-1, "default");
}


status_t 
DVBMediaNode::GetParameterValue(int32 id, bigtime_t *last_change, void *value,
        size_t *size)
{
//      TRACE("DVBMediaNode::GetParameterValue, id 0x%lx\n", id);
        
        switch (id) {
                case ID_STATE:
                        *size = 4;
                        *(int32 *)value = fSelectedState;
                        break;

                case ID_REGION:
                        *size = 4;
                        *(int32 *)value = fSelectedRegion;
                        break;

                case ID_CHANNEL:
                        *size = 4;
                        *(int32 *)value = fSelectedChannel;
                        break;

                case ID_AUDIO:
                        *size = 4;
                        *(int32 *)value = fSelectedAudio;
                        break;

                default:
                        return B_ERROR;
        }
        return B_OK;
}


void
DVBMediaNode::SetParameterValue(int32 id, bigtime_t when, const void *value,
        size_t size)
{
        TRACE("DVBMediaNode::SetParameterValue, id 0x%lx, size %ld, value 0x%lx\n",
                id, size, *(const int32 *)value);
        
        switch (id) {
                case ID_STATE:
                        fSelectedState = *(const int32 *)value;
                        StopCapture();
                        RefreshRegionList();
                        RefreshChannelList();
                        RefreshAudioList();
                        EventQueue()->AddEvent(media_timed_event(0,
                                M_REFRESH_PARAMETER_WEB));
//                      Tune();
                        break;
                        
                case ID_REGION:  
                        fSelectedRegion = *(const int32 *)value;
                        StopCapture();
                        RefreshChannelList();
                        RefreshAudioList();
                        EventQueue()->AddEvent(media_timed_event(0,
                                M_REFRESH_PARAMETER_WEB));
//                      Tune();
                        break;
                        
                case ID_CHANNEL: 
                        fSelectedChannel = *(const int32 *)value;
                        RefreshAudioList();
//                      EventQueue()->AddEvent(media_timed_event(0,
//                              M_REFRESH_PARAMETER_WEB));
                        Tune();
                        break;
                        
                case ID_AUDIO:   
                        fSelectedAudio = *(const int32 *)value;
                        Tune();
                        break;
                        
                default:
                        break;
        }
        TRACE("DVBMediaNode::SetParameterValue finished\n");
}


status_t
DVBMediaNode::ExtractTuningParams(const char *description, int audio_pid_index,
        dvb_tuning_parameters_t *tuning_param, int *video_pid, int *audio_pid,
        int *pcr_pid)
{
        if (!description)
                return B_ERROR;

        printf("ExtractTuningParams: \"%s\"\n", description);
                
        char name[50];
        char freq[50];
        char para[100];
        char src[50];
        char srate[50];
        char vpid[50];
        char apid[50];
        char tpid[50];
        char ca[50];
        char sid[50];
        char nid[50];
        char tid[50];
        char rid[50];
        
        sscanf(description, " %[^:] : %[^:] : %[^:] : %[^:] : %[^:] : %[^:] : %[^:]"
                " : %[^:] : %[^:] : %[^:] : %[^:] : %[^:] : %[^:] ", name, freq, para,
                src, srate, vpid, apid, tpid, ca, sid, nid, tid, rid);
                
        char *cpid = strchr(vpid, '+');
        if (cpid) cpid++;
                
        int _vpid = strtol(vpid, 0, 0);
        int _apid = strtol(apid, 0, 0);
//      int _tpid = strtol(tpid, 0, 0);
        int _cpid = cpid ? strtol(cpid, 0, 0) : _vpid;
        int _srate = strtol(srate, 0, 0);
        int64 _freq = strtol(freq, 0, 0);
        while (_freq && _freq <= 1000000)
                _freq *= 1000;

        if (fInterfaceType == DVB_TYPE_DVB_S && _freq < 950000000) {
                TRACE("workaround activated: type is DVB_S and frequency < 950 MHz,"
                        " multiplying by 1000!\n");
                _freq *= 1000;
        }


        *video_pid = _vpid;
        *audio_pid = _apid;
        *pcr_pid   = _cpid;

        TRACE("parsing result: params: '%s'\n", para);
        
        TRACE("parsing result: video pid %d\n", _vpid);
        TRACE("parsing result: audio pid %d\n", _apid);
        TRACE("parsing result: PCR pid %d\n", _cpid);
        TRACE("parsing result: symbol rate %d\n", _srate);
        TRACE("parsing result: Frequency %lld Hz, %lld MHz\n", _freq, _freq / 1000000);

        if (fInterfaceType == DVB_TYPE_DVB_T) {

                dvb_t_tuning_parameters_t *param = &tuning_param->u.dvb_t;

                TRACE("Interface is DVB-T\n");
                param->frequency = _freq;
                param->inversion = DVB_INVERSION_OFF;
                param->bandwidth = DVB_BANDWIDTH_8_MHZ;
                param->modulation = DVB_MODULATION_16_QAM;
                param->hierarchy = DVB_HIERARCHY_NONE;
                param->code_rate_hp = DVB_FEC_2_3;
                param->code_rate_lp = DVB_FEC_2_3;
                param->transmission_mode = DVB_TRANSMISSION_MODE_8K;
                param->guard_interval = DVB_GUARD_INTERVAL_1_4;
        }

        if (fInterfaceType == DVB_TYPE_DVB_S) {
                dvb_s_tuning_parameters_t *param = &tuning_param->u.dvb_s;

                TRACE("Interface is DVB-S\n");

                const char *pInv = strchr(para, 'I');
                if (pInv == NULL)
                        pInv = strchr(para, 'i');
                if (pInv != NULL && pInv[1] == '0') {
                        TRACE("DVB_INVERSION_OFF\n");
                        param->inversion = DVB_INVERSION_OFF;
                } else if (pInv != NULL && pInv[1] == '1') {
                        TRACE("DVB_INVERSION_ON\n");
                        param->inversion = DVB_INVERSION_ON;
                } else {
                        TRACE("parse error, assuming DVB_INVERSION_OFF\n");
                        param->inversion = DVB_INVERSION_OFF;
                }

                const char *pPolH = strchr(para, 'H');
                if (pPolH == NULL)
                        pPolH = strchr(para, 'h');
                const char *pPolV = strchr(para, 'V');
                if (pPolV == NULL)
                        pPolV = strchr(para, 'v');
                if (pPolH != NULL && pPolV == NULL) {
                        TRACE("DVB_POLARITY_HORIZONTAL\n");
                        param->polarity = DVB_POLARITY_HORIZONTAL;
                } else if (pPolH == NULL && pPolV != NULL) {
                        TRACE("DVB_POLARITY_VERTICAL\n");
                        param->polarity = DVB_POLARITY_VERTICAL;
                } else {
                        TRACE("parse error, assuming DVB_POLARITY_HORIZONTAL\n");
                        param->polarity = DVB_POLARITY_HORIZONTAL;
                }
                
                param->frequency = _freq;
                param->symbolrate = _srate;
        }
        
        return B_OK;
}