Recording and cutting DVB recordings

September 2, 2008 at 3:40 PMAndre Loker

[This article has nothing to do with .NET and/or programming]

Here's a brief guide that describes how I record and cut DVB (digital video broadcasting) recordings.

Hardware and software

  • Terrate Cinergy 1400 DVB-T
    This PCI DVB-T tuner is rather old, but it runs fine on Vista x64
  • DVBViewer (15€)
    Multifunctional playback and recording suite which supports for the Cinergy 1400 through the BDA drivers
  • DVBViewer VideoRecorder Plugin (free, requires DVBViewer)
    Great extension to DVBViewer which allows overlapping recording (only if all programs come from the same transponder of course)
  • ProjectX (free)
    Excellent program to demultiplex and repair the recorded TS files. Some use PVAStrumento as an alternative.
  • Cuttermaran (free)
    Powerful cutting tool
  • Batch Cuttermaran (free)
    Cuttermaran does not come with batch processing capabilities. Batch Cuttermaran is a small utility that can process an arbitrary number of Cuttermaran projects in a batch.
  • For DVD authoring: Gui for dvdauthor, dvdauthorgui, DVD Flick or DVD Styler

Steps

1. Record TV program

I use the DVBViewer VideoRecorder plugin for recording. While most settings can be left as they are, I change two settings: first, I always record to the TS format (transport stream) . Second, I enable "+ Additional Audio" under the Channels tab.

imageimage

2. Demux recordings

After I've recorded a bunch of programs I demux them using ProjectX.

image

Before you demux for the first time you'd want to check some settings. For general preferences use the PreSettings menu. To configure the demux process for the first time select "prepare >>" in the main window. Be sure "demux" is selected and enable "Process all collections" in the "Edit" menu.

image image

To demux a single file, use the + button in the main window (blue rectangle in the image above) and browse for that file. After it has been added use "QuickStart" to start demuxing.

To demux multiple files in a batch you need to put each file in a separate file collection. If you add multiple files into the same collection (using the + button in the blue rectangle) all files will be joined during demuxing. Instead, for each file to demux use the + button in the red rectangle to create a new collection, add a file using the "blue" + button.

Demuxing a DVB-T recording of several GB size takes some time of course. This is why I prefer to demux several files at once. I can start the demuxing process and can leave the computer alone for an hour or so.

3. Define cuts

Now that we have our demuxed files we can cut them (trim start and end, cut commercials etc.). Using Cuttermaran this is fairly easy. For each part in the recording you want to keep use the In and Out buttons to define the start and end frame and use the + button to add this part to the cut list.

image

If you've defined all entries in the cut list, don't start physical yet! Just save the project to disk and continue with the next recording you need to cut.

4. Process cuts

After you've saved Cuttermaran project files for all recordings use Batch Cuttermaran to process the cutting physically. This again takes some time which is why we want to do it in an unattended batch process.

Note: Cuttermaran uses elementary streams as input and ouput. If you want to multiplex the streams back to e.g. an mpeg file, you can define this in the Cuttermaran settings:

image

When Batch Cuttermaran executes the project files the settings made in Cuttermaran come into effect, which means that either all or none of the projects will be muxed during batch processing. Personally I prefer to mux the files to MPEG and delete the elementary streams as I watch the recordings on my Computer. If you want to create a real DVD you'll probably don't want to mux the streams but use tools like GUI for dvdauthor  or dvdauthorgui for further DVD authoring. Having the streams muxed to MPEG is no problem however as they can be easily demuxed if you need to.

Posted in: Off topic

Tags:

The true meaning of readonly for value types

August 27, 2008 at 5:47 PMAndre Loker

I've been trying to create a simple and lightweight wrapper around List<T> and List<T>.Enumerator. The only thing I needed was the ability to enumerate over the items of the list. The list is supposed to be enumerated very(!) often, so I wanted the enumerator to be struct instead of a class. Using a value type enumerator avoids any heap allocation during enumerator. List<T>.Enumerator is a value type just because of the same reason.

So here's my first approach:

   1: /// <summary>
   2: /// A wrapper around an <see cref="List{T}"/> that only allows enumeration. 
   3: /// </summary>
   4: public struct ReadOnlyList<T> {
   5:   private readonly List<T> items;
   6:  
   7:   /// <summary>
   8:   /// Initializes a new instance of the <see cref="ReadOnlyList{T}"/> struct.
   9:   /// </summary>
  10:   /// <param name="list">The list.</param>
  11:   public ReadOnlyList(List<T> list) {
  12:     items = list;
  13:   }
  14:  
  15:   /// <summary>
  16:   /// Gets the enumerator.
  17:   /// </summary>
  18:   /// <returns></returns>
  19:   public Enumerator GetEnumerator() {
  20:     return new Enumerator(items);
  21:   }
  22:  
  23:   #region Nested type: Enumerator
  24:   /// <summary>
  25:   /// A light weight enumerator for <see cref="ReadOnlyList{T}"/>
  26:   /// </summary>
  27:   public struct Enumerator {
  28:     private readonly List<T>.Enumerator enumerator;
  29:  
  30:     /// <summary>
  31:     /// Initializes a new instance of the <see cref="ReadOnlyList&lt;T&gt;.Enumerator"/> struct.
  32:     /// </summary>
  33:     /// <param name="items">The items.</param>
  34:     public Enumerator(List<T> items) {
  35:       enumerator = items.GetEnumerator();
  36:     }
  37:  
  38:     /// <summary>
  39:     /// Gets the current item.
  40:     /// </summary>
  41:     /// <value>The current item.</value>
  42:     public T Current {
  43:       get { return enumerator.Current; }
  44:     }
  45:  
  46:     /// <summary>
  47:     /// Moves to the next element in the enumeration.
  48:     /// </summary>
  49:     /// <returns></returns>
  50:     public bool MoveNext() {
  51:       return enumerator.MoveNext();
  52:     }
  53:   }
  54:   #endregion
  55: }

This looks perfectly reasonable if you ask me. Here's a unit test to check that the enumerator works:

   1: [Test]
   2: public void Enumerator_CanEnumerateList() {
   3:   var list = new List<int> {0, 1, 2, 3};
   4:   var enumerator = new ReadOnlyList<int>.Enumerator(list);
   5:   var expected = 0;
   6:   while(enumerator.MoveNext()) {
   7:     Assert.AreEqual(expected, enumerator.Current);
   8:     expected++;
   9:   }
  10:   Assert.AreEqual(list.Count, expected);
  11: }

The test should pass, right? After all the Enumerator struct is a mere wrapper aroung List<T>.Enumerator. Funny thing is: it fails! Do you see why? As a hint, here's what fails: the second time the loop runs enumerator.Current is 0 although enumerator.MoveNext() has returned true. But why? It has taken me quite a while to find the reason.

To the Bat Mobile Debugger!

Of course, first thing I did was to debug the code. List<T>.Enumerator.MoveNext() looks something like this:

   1: public bool MoveNext() {
   2:     List<T> list = this.list;
   3:     if ((this.version == list._version) && (this.index < list._size)) {
   4:         this.current = list._items[this.index];
   5:         this.index++;
   6:         return true;
   7:     }
   8:     return this.MoveNextRare();
   9: }

MoveNextRare() is not important here, but what is important is that when the method returns true, index should have been increased. However, according to the debugger, the value of enumerator.enumerator.index doesn't change after the call to MoveNext(), although it returned true. Weird, huh? Somehow the fields did not get updated.

So I dug deep into the code. I opened ReadOnlyList<T>.Enumerator.MoveNext() in Reflector and here's what I got:

   1: .method public hidebysig instance bool MoveNext() cil managed
   2: {
   3:     .maxstack 1
   4:     .locals init (
   5:         [0] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!T> CS$0$0000)
   6:     L_0000: ldarg.0 
   7:     L_0001: ldfld valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> Moonbow.Util.ReadOnlyList`1/Enumerator<!T>::enumerator
   8:     L_0006: stloc.0 
   9:     L_0007: ldloca.s CS$0$0000
  10:     L_0009: call instance bool [mscorlib]System.Collections.Generic.List`1/Enumerator<!T>::MoveNext()
  11:     L_000e: ret 
  12: }

Now it became clear that my MoveNext() method created a local copy of the enumerator field and called MoveNext on the copy. This of course meant that the original fields of the List<T>.Enumerator wouldn't get updated. And now you might see where my mistake was? Right: ReadOnlyList<T>.Enumerator.enumerator is marked as readonly! My idea was that the enumerator object will not be reassigned in any method of my Enumerator, so I could as well make the field readonly. After removing the readonly modifier the IL of my MoveNext() method was much more like I expected:

   1: .method public hidebysig instance bool MoveNext() cil managed
   2: {
   3:     .maxstack 8
   4:     L_0000: ldarg.0 
   5:     L_0001: ldflda valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> Moonbow.Util.ReadOnlyList`1/Enumerator<!T>::enumerator
   6:     L_0006: call instance bool [mscorlib]System.Collections.Generic.List`1/Enumerator<!T>::MoveNext()
   7:     L_000b: ret 
   8: }

The conclusion

Here's the moral of the story:

If you declare a value type field as readonly, every call made to this field is actually made to a local copy. Any changes made in those calls are not stored in the readonly field. This is different from reference type fields where calls will be made on the field itself, potentially causing changes in the referenced object.

If this has been obvious for you: kudos, I didn't know that. Rreadonly fields are "really" readonly for value types, it means more than prohibiting assignments to the field. I did not find a word about that in the specification. The MSDN only mentions that assignments to readonly fields are only allowed in the declartion or in the constructor:

The readonly keyword is a modifier that you can use on fields. When a field declaration includes a readonly modifier, assignments to the fields introduced by the declaration can only occur as part of the declaration or in a constructor in the same class.

There is however a bug report that deals with this issue. Is has been closed as "By Design", though. It references the part in the specs that defines this behaviour: it's in section 7.4.4, function member invocation, bullet list 2, item 2, subitem 2.

If E is not classified as a variable, then a temporary local variable of E’s type is created and the value of E is assigned to that variable. E is then reclassified as a reference to that temporary local variable. The temporary variable is accessible as this within M, but not in any other way. Thus, only when E is a true variable is it possible for the caller to observe the changes that M makes to this.

By the way: it does not matter whether the type that declares the readonly value type fiels is a struct or a class by itself.

Posted in: C#

Tags: ,

RedGate acquires .NET Reflector

August 26, 2008 at 2:23 PMAndre Loker

This morning I got an e-mail from Lutz Roeder, creator of the marvelous .NET Reflector in which he announces that he handed development of .NET Reflector over to Red Gate Software. Red Gate is known for their .NET profiler ANTS and has also acquired the PInvoke.net plugin (originally developed by PInvoke.net)some time ago.

What's probably important for many of us is that Red Gates promises to provide a free version of Reflector:

We will continue to maintain a free version for the benefit of the community.

On the one hand this opens a big opportunity for the future of .NET Reflector. Red Gate is certainly capable of adding a lot of functionality to Reflector. On the other hand, I see the potential danger that Red Gate might neglect the development of a free community version in the course of time in favour of a commercial license.

I can totally understand Lutz when he says that after eight years of development he's striving for new objectives. And I can also understand that by handing Reflector over to Red Gate he can get the financial reward that he deserves. It would of course have been awesome if he made Reflector open source, but he chose not to, and I respect this choice. Let's just cross fingers that Red Gate decides to put as much effort into a free community version as in a commercial version.

To Lutz, all the best for future projects. Thanks for having made such a great piece of software.

To Red Gate, good luck with the further development of Reflector. You're in the responsibility of keeping the spirit of a great product alive.

Posted in: Tools

Tags:

How to enable sound in Remote Desktop sessions on WinServer 2k3

August 26, 2008 at 1:46 PMAndre Loker

On a Windows Server 2003 machine I needed sound to be enabled during remote desktop connections. Here's what I had to do to bring the sound to the client machine.

  1. Install audio drivers on the server, of course
  2. Enable the Windows Audio service on the server.
    1. Open Administrative Tools => Services
    2. Locate the service named Windows Audio
    3. Set the start mode to "Automatic" and start the service
      image
  3. Enable RDP-Tcp Audio Mapping
    1. Open Administrative Tools => Terminal Services Configuration
    2. Select the "Connections" node on the left side
    3. Right click on RDP-Tcp on the right side and select "Properties" (or double click RDP-Tcp)
    4. On the "Client Settings" tab, uncheck "Audio mapping"  (checked items are disabled)
      image
  4. In the Remote Desktop Connection window (client side), enable audio playback on client computer
    1. Show options by clicking "Options >>"
    2. On the "Local Resources" tab, select "Bring to this computer" in the combo box under "Remote computer sound"
      image
  5. Connect to the remote server and you should have sound.

Note: you should only enable sound on the server if you have a good reason, e.g. I edit my DVB-T recordings on the server. Otherwise, leave audio off for stability and security reasons.

Posted in: Windows

Tags: , ,

The almighty boolean toggle

August 5, 2008 at 10:53 PMAndre Loker

Do you the feeling when you know exactly what you mean, but you can't find a succinct way to express it? The guy that posted this classic piece of code to the forum surely does:

   1: if (flag == false)
   2:   flag = true;
   3: else if (flag == true)
   4:   flag = false;

Hint: flag = !flag

Sorry for not posting anything of greater importance. I'm rather tired :-)

Posted in: Snippets

Tags: