BBC Programmzeitschrift – die 2te

Kurz zur Erinnerung zum Hintergrund: praktisch alle Sender strahlen in der so genannten Programmzeitschrift (EPG) Informationen zur aktuellen und nächsten Sendung aus. Bei deutschen Sendern ist es darüber hinaus üblich, dass auch Informationen zu den folgenden Sendungen der nächsten Tage als Vorabinformationen ausgestrahlt werden – zum Teil bis zu einer Woche um Voraus, lobend zu erwähnen sind vor allem die Öffentlich-Rechtlichen Sender aber noch mehr die RTL Gruppe, die im Gegensatz zu ÖR auch dabei noch mit detaillierten Daten nicht geizen.

Bei den englischen Sendern (BBC / ITV via Astra 2 auf 28.2° Ost) ist das leider nicht so. Hier werden erweiterte Sendungsdaten in einem properitären, nicht öffentlichen oder zumindest nicht dokumentierten Format übertragen. Schon in der letzten Version von DVB.NET / VCR.NET hatte ich etwas recherchiert, bin aber auf keinen grünen Zweig gekommen. Auf für die 3.5 muss ich jetzt meine Untersuchungen einstellen, da ich das mir selbst zugeteilte Zeitbudget doch schon deutlich überschritten habe. Also wieder leider keine Programmzeitschrift für BBC und Konsorten in VCR.NET 3.5.

Bei den Untersuchungen sind als Abfallprodukt einige .NET Klasse (in der EPG Assembly) hinzugekommen, die einige der SI Tabellen analysieren können, die von dem properitären System übertragen werden (nicht MHP, sondern OpenTV, aber nach Allem, was man so findet, war es schon vor Jahren mit dem Open in OpenTV Sense). Insbesondere gibt es eine Unterstützung zur Rekonstruktion der so genannten Module aus Einzelfragmenten und deren Dekomprimierung – die zwar nicht wirklich schwierig, aber doch etwas ungewöhnlich ist. Der Code ist experimentell und sicher noch nicht ausgereift, was etwa Stabilität angeht. Aber immerhin geht es erst einmal und dass muss hier und heute reichen.

Ein bisschen mehr Recherche scheint darauf hin zu deuten, dass die EPG Daten möglicherweise gar nicht in den OpenTV Datenströmen enthalten sind, sondern dass hier die DVB-SI Data Caroussell Technik verwendet wird. Dazu vielleicht mehr in der nächsten Version meiner DVB Tools.

So long

Jochen

Interprozesskommunikation für Arme

Im Rahmen der Live Demux Erweiterung von VCR.NET ergab sich folgendes Problem: ein Dienst startet ein Programm A und wartet, bis dieses beendet wird. Dieses Program wiederum startet ein weiteres Programm B. In seltenen Fällen kommt es dazu, dass A sich selbst vor B beendet (ist eine Schutzmassnahme, Details führen hier zu weit). Das hat aber leider den Effekt, dass der Dienst denkt, die Arbeit von A wäre erledigt – ist sie aber nicht.

Die auf die Schnelle implementierte Lösung ist recht einfach und nutzt eine Technik, die möglicherweise nicht weit verbreitet ist, aber deren Anwendung extrem einfach ist. Zudem hier ein erhebliches Potential steckt, dazu mehr am Ende.

Die Änderung ist, dass beim Starten von A auf einen speziellen Befehlszeilenparameter geprüft wird, der sicher nie verwendet wird – hier: /Slave. Ist dieser nicht vorhanden, so startet A sich selbst und fügt diesen Parameter an die Befehlszeile an. Ist dann /Slave gesetzt, so führt A die gewohnen Arbeiten durch, fast wie vorher.

Hier der Code zum Starten der zweiten A Instanz:

// Parameter helper
StringBuilder cmdLine = new StringBuilder();

// Append switch
cmdLine.Append("/Slave");

// Merge all
foreach (string arg in realArgs) cmdLine.AppendFormat(" \"{0}\"", arg.Replace("\"", "\"\""));

// Create start information
ProcessStartInfo info = new ProcessStartInfo();

// Configure
info.Arguments = cmdLine.ToString();
info.FileName = Application.ExecutablePath;
info.LoadUserProfile = true;
info.UseShellExecute = false;
info.RedirectStandardOutput = true;
info.RedirectStandardInput = true;

// Create the process
Process process = Process.Start(info);

Interessant ist, dass eine Umleitung von Standardeingabe und Ausgabe angefordert wird. In der zweiten A Instanz beim eigentlichen Arbeiten wurde nun nach Starten von B nur folgender Code ergänzt – sämtliche Restfunktionalität bleibt unverändert:

// Create the process
m_Demux = Process.Start(info);

// Send to controller
Console.Out.WriteLine(m_Demux.Id);
Console.Out.Flush();

// Synchronize with caller
Console.In.ReadLine();

Die echte A Instanz sendet die Prozesskennung von B an die steuernde A Instanz und wartet dann auf eine Freigabe von dieser. Dieses Warten ist wichtig, damit B nicht schon fertig ist und die Prozesskennung schon wieder neu vergeben ist, bevor die Steuerinstanz die Rückgabe auswertet – sehr unwahrscheinlich, aber nicht unmöglich! Die Steuerinstanz liest nun die Kennung und wartet, bis die echte A Instanz und B beendet sind:

// Read the process identifier of the sub process
int demuxId = int.Parse(process.StandardOutput.ReadLine());

// Attach to the process
Process demux;

// Load
demux = Process.GetProcessById(demuxId);

// Let it continue
process.StandardInput.WriteLine("OK");
process.StandardInput.Flush();

// Wait for outer
process.WaitForExit();

// Wait for inner
if (null != demux) demux.WaitForExit();

Diese einfache Kommunikation eines Prozesses mit einem gestarteten Kindprozeß über die Standardkanäle hat eine sehr mächtige Erweiterungsmöglichkeit: es ist genau so einfach möglich, über den BinaryFormatter serialisierbare Objekte zwischen den Prozessen auszutauschen. Also erzeugt die Steuerinstanz etwa ein solches Objekt, initialisiert es und serialisiert es dann auf StandardInput des Kindprozesses. Dieser wartet mit einer Deserialisierung auf seinem Console.In, rekonstruiert das Objekt, führt entsprechende Aktionen aus und sendet via Console.Out und BinaryFormatter eine Antwort an die Steuerinstanz.

Genauso kommuniziert VCR.NET sehr erfolgreich mit den Aufzeichnungsprozessen.

Happy Coding

Jochen

Spaß mit Visual Studio 2008 – hier: Deployment Projekte

Da denkt man sich, man nimmt mal eben so ein Visual Studio 2005 Projekt, lädt es in Visual Studio 2008 und alles ist hipp. Naja, zumindest bei MSI Setups (Deployment Projekte) hat sich Microsoft erlaubt, einige Fehler leicht inkompatibel zu beheben.

Da ist erst einmal ein Upgrade Setup. Früher war es so, dass bei einem Upgrade erst die alte Version deinstalliert wurde und dann die neuen Dateien installiert wurden. Diese Reihenfolge wurde umgekehrt (ohne eine direkte Konfiguration, die den alten Zustand wieder herstellt). Mit dem Effekt, dass eine Datei nur noch dann ersetzt wird, wenn sich die Dateiversion (nicht der Inhalt) ändert. Bei DVB.NET ist es etwa so, dass alle Dateien die Versionsnummer des Produktes tragen, also 3.2 oder 3.5. Also kein Problem? Leider doch: während der Beta-Phase ändere ich die Versionsnummern im Allgemeinen nicht. Wird also eine 3.5 Beta über eine andere eingespielt, werden die Dateien nicht ausgetauscht! Fazit im Moment: das Setup prüft, ob bereits eine Installation mit gleicher Versionsnummer vorliegt und verweigert dann seinen Dienst mit dem Hinweis, die vorhandene Version zu deinstallieren. Das ärgert für DVB.NET erst mal nur die Beta-Tester, bei VCR.NET leider auch den normalen Endanwender.

Meiner Ansicht nach noch einen Zahn schärfer ist das Ausführen so genannter Custom Actions. Beim VCR.NET etwa sorgt eine solche Custom Action dafür, dass die Konfiguration der Vorgängerversion aus der .EXE.CONFIG.CPY in die .EXE.CONFIG eingepflegt wird, die von der Installation als Template mitgeliefert wird. Bei einer ‚All Users‘ Installation laufen Custom Actions nun (wieder nicht durch eine direkte Konfiguration im Visual Studio zu beeinflussen) unter dem Konto LocalSystem. Technisch gesehen ist das auch gut so, da eine ‚All Users‘ Installation auch Zugriff auf Ressourcen haben muss, die dem normalen Anwender verwehrt sind. Leider startet beim VCR.NET die Custom Action aber auch die Überwachung. Die erscheint dann auch brav sichtbar, kennt aber nicht die Konfiguration des aktuellen Anwenders und läuft ebenso als LocalSystem. Das ist hier nicht im Sinne des Erfinders. Bis zu einer guten Lösung habe ich das Feature erst mal deaktiviert, der Anwender muss nach der Installation nun die Überwachung manuell in seinem Kontext starten – oder sich ab- und wieder anmelden, da die Überwachung in der Autostart Gruppe landet.

Selbst wenn diese Änderungen durchaus sinnvoll sind (zumindest in fast allen Fällen) hätte ich mir bei einer derart inkompatiblen (breaking change) Änderung schon gewünscht, dass man das alte Verhalten leicht wieder herstellen kann (und nicht durch Manipulation der fertigen MSI mit irgendwelchen COM Schnittstellen). Naja, zuviel erwartet…

Frohes Codieren

Jochen

Zusatz: Hier ein kleiner Vortrag, den ich voraussichtlich am 22. April 2008 bei Bonn-to-Code halten werde.

DVB.NET und .NET 3.5

Ich plane locker, DVB.NET 3.5 auch auf Visual Studio 2008 und .NET 3.5 umzustellen – die Koinzidenz der Versionsnummern ist zufällig. Ich werde dazu noch eine Umfrage im Forum machen, evtl. auch .NET 2.0, aber auf jeden Fall Visual Studio 2008.

Vorab habe ich schon mal geprüft, ob ich auch die BDAWindow Klasse auf WPF umstelle, werde davon aber absehen. Der Lernaufwand um es wenigstens etwas ordentlich zu machen ist mir im Moment zu hoch und ich habe eine ganze Liste anderer kleinerer Wünsche, die ich angehen möchte.

Was ich aber gemacht habe, ist mal zu schauen, wie schwierig es ist, eine WPF Anwendung zu machen, die mit DVB.NET ein Signal anzeigt. Hier der kurze Bericht, vielleicht reizt das ja den einen oder anderen, auch einmal herum zu spielen. DVB.NET 3.2 ist Installationsvoraussetzung, dito Visual C# 2008 – ich weiß nicht, ob die Express Edition reicht, kann aber gut sein.

Als erstes legen wir eine neue WPF Anwendung namens DVBNETSample an. Als zusätzliche Referenzen werden JMS.DVB.DLL und JMS.DVB.Provider.BDA.DLL benötigt – TechnoTrend Premium Anwender sollten der Einfachheit halber auch noch JMS.DVB.Provider.TTPremium.DLL hinzufügen und die TTDVBACC.DLL dorthin kopieren, wo später die DVBNETSample.EXE ausgeführt wird.

Auf dem Fenster Window1 wird nun ein WindowsFormsHost Control angelegt und zum Beispiel so ausgerichtet, dass es das ganze Window1 ausfüllt. Vermutlich geht das Folgende auch einfacher, aber da ich VS2008 erst seit einer knappen Stunde nutze hier mein Weg: in der XAML Ansicht habe ich auf dem Window Root Tag die Eigenschaft xmlns:dvbnet=“clr-namespace:JMS.DVB.BDA;assembly=JMS.DVB.Provider.BDA“ ergänzt. Nun kann man im WindowsFormsHost Tag als Kindknoten <dvbnet:BDAWindow/> einfügen. Startet man die Anwendung jetzt, dann gibt es ein blaues Fenster. Ein gutes Zeichen, denn das WinForms BDAWindow läuft nun in der WPF Anwendung – das war eigentlich auch nicht anders zu erwarten.

Nun sind noch zwei Schritte zu erledigen. In der Anwendungsdatei App.xml.cs wird DVB.NET initialisiert und sauber terminiert.

public partial class App : Application
{
    private JMS.DVB.IDeviceProvider m_Device;

    protected override void OnStartup(StartupEventArgs e)
    {
        m_Device = JMS.DVB.Tools.CreateProvider();

        if (null == m_Device)
            Environment.Exit(1);
        else            
            base.OnStartup(e);
    }

    protected override void OnExit(ExitEventArgs e)
    {
        if (null != m_Device)
            m_Device.Dispose();

        base.OnExit(e);
    }

    public JMS.DVB.IDeviceProvider DVBDevice
    {
        get
        {
            return m_Device;
        }
    }
}

Und dann noch ein bisschen Code, um beim Starten der Anwendung den Sender ZDF auszuwählen, den Datenempfang zu aktivieren und Video sowie erste Tonspur an das BDAWindow zu schicken. Durch Doppelklick auf Window1 wird eine Methode angelegt und wie folgt gefüllt.

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    JMS.DVB.IDeviceProvider device = ((App)App.Current).DVBDevice;

    JMS.DVB.Station station = device.ResolveStation("ZDF", null)[0];

    device.Tune(station.Transponder);
    device.SetVideoAudio(0, 0, 0);

    JMS.DVB.AudioInfo[] audios = station.AudioMap;

    JMS.DVB.BDA.BDAWindow wnd = (JMS.DVB.BDA.BDAWindow)windowsFormsHost1.Child;
    JMS.DVB.BDA.AccessModules.AudioVideoAccessor accessor = new AudioVideoAccessor();

    wnd.SetAccessor(accessor);

    accessor.StartGraph(2 != station.VideoType, audios[0].AC3);

    device.RegisterPipingFilter(station.VideoPID, true, false, accessor.AddVideo);
    device.RegisterPipingFilter(audios[0].PID, false, false, accessor.AddAudio);

    device.StartFilter(station.VideoPID);
    device.StartFilter(audios[0].PID);
}

Und das war’s schon

Geht auch mit HDTV

Viel Spass

Jochen

Nullable<int> (int?) und +=

Na gut, vermutlich ist das ja ordentlich dokumentiert und ich war nur zu faul zum Suchen, aber hier mal eine kleine Erfahrung. Was erwartet man als Ergebnis von a += b; wenn a und b beide Nullable<int>, also int? sind?

Erst einmal ist der Code semantisch äquivalent zu a = a + b, also wird der Wert von a durch die Summe ersetzt – das ist in C# nur natürlich, liefert ja += für eine Zeichenkette auch eine neue Instanz, wenn die rechte Seite keine leere Zeichenkette ist.

Aber was ist denn nun a + b? Das Ergebnis ist null, wenn einer der Werte null ist und ansonsten wie zu erwarten die Summe der int Werte. Denkt man etwas darüber nach, kann es gar nicht anders sein, tut man das aber nicht (Nachdenken), ist man vielleicht überrascht, dass kein Fehler auftritt, sondern einfach eine null erscheint.

Beim Anschauen des tatsächlich erzeugten IL Codes ist mir auch die Methode Nullable.GetValueOrDefault aufgefallen, die ich vorher nur unterbewußt wahrgenommen habe. Bisher habe ich oft das Konstrukt a ?? 0 verwendet. Wenn allerdings der Rückfallwert der Defaultwerte des Datentyps ist (0, false, …), dann ist die Methode sehr viel effizienter.

Nur so Kleckerkram am Rande

Jochen