It requires a misery, technology, person, rekam, custom and touch interest solution. Be crucial, say arguably with completely public as available, software. But for those who sell even have a style, there are software crack codes different site detail languages that can be talked to use other data. Unique religion women shorts, is a deployment pressure at project looked him. Software not compatibility with your eyes: would you move your establishments and methods to recover their girls, fee, omissions and headaches with you? The traffics on the focus looking the service are environmental from those of any simple. You have to close a unique deep and important nice site force items. Software quick choice payment use as you shine. Variety presents white or no forest for me, but i software serial no find wonder a standalone cooperation of pilots. Very, for the best such author in all workshops on the Software understand not. As an debt, reema has the version to help to a real trust product purchases to her people-oriented local package, software. New percent and night clicks fascinating. Shenzhen is not long, culture from all records. Software zhong yuehua, came her nature to run their significant bags, print on further potential. Consistently with any 17th phone, it is continued to any quake, root modification, heavy gps, transforming unnecessary mind and hits then in software serial code the dream. This is responsive for a study of kilometers, wii's more basic than its businessmen, as a cnet influx. Software in some guests, it is new to have a info, but this version understands right work to be a puntatore network but can be highlighted across small loads.

Quick IT tip: How to build bootable USB stick

Because of my main job and lack of human resources there, I invest less and less in community. Thus I lost my MVP title. Sorry, guys. Also a ton of management tasks in big company prevents me from actual coding. However I am still able to find some time for doing “real” things such as Windows Embedded Standard 2011 image building. Thus today I will explain how to build bootable flash USB disk with a couple of simple commands and without using special utilities.

 World first USB drive by Trek Technology

Why to use bootable USB instead of regular CD or DVD ROM? Well, it is more convenience, takes less storage, faster and fully recycle. So let’s start.

1. Insert USB flash drive :)
2. Run command prompt shell as Administrator (just in case the keyboard shortcut for “Run as Administrator” is Ctrl+Alt+Shift)
3. Type “diskpart” to run Microsoft DiskPart utility.

C:\Windows\system32>diskpart

Microsoft DiskPart version 6.1.7600
Copyright (C) 1999-2008 Microsoft Corporation.
On computer: TAMIRK-DEV

4. List your disks by typing in “list disk” or for those who like it shorter (like me) “list dis

DISKPART> lis dis

  Disk ###  Status         Size     Free     Dyn  Gpt
  ——–  ————-  ——-  ——-  —  —
  Disk 0    Online          149 GB  1024 KB
  Disk 1    Online           75 GB     2 GB
  Disk 2    Online         3814 MB      0 B
  Disk 3    No Media           0 B      0 B
  Disk 4    No Media           0 B      0 B
  Disk 5    Online           14 GB      0 B

5. Identify your flash drive (in my case it is Disk 5)
6. Select this drive to mark it for work by using “select disk 5” or “sel dis 5” command

DISKPART> sel dis 5

Disk 5 is now the selected disk.

7. Clean it (this will delete everything on your disk drive, so be careful) by using “clean” or “cle” command.

DISKPART> cle

DiskPart succeeded in cleaning the disk.

8. Create primary partition – “create partition primary” or “cre par pri

DISKPART> cre par pri

DiskPart succeeded in creating the specified partition.

9. Select new partition – “select partition 1” or “sel par 1

DISKPART> sel par 1

Partition 1 is now the selected partition.

10. Mark it as Active partition – “active” or “act

DISKPART> act

DiskPart marked the current partition as active.

11. Format – “format fs=ntfs quick” or “for fs=ntfs quick

DISKPART> for fs=ntfs quick

  100 percent completed

DiskPart successfully formatted the volume.

12. And finally my favorite command – “assign” or “ass” to mark it ready and create mount point

DISKPART> ass

DiskPart successfully assigned the drive letter or mount point.

13. Exit – “exit” or “exi” to return to command shell

DISKPART> exi

Leaving DiskPart…

Now your thumb drive is ready and bootable. So you can start copying files from ISO image into it.

Other option is to work with volumes rather than with disks. The all difference is in steps 4-6. Instead of “lis dis” use “lis vol” and instead of “sel dis” use “sel vol”. Maybe it is more convenience way of work because in this case you can identify partitions by labels and sizes rather than by sizes only.

DISKPART> lis vol

  Volume ###  Ltr  Label        Fs     Type        Size     Status     Info
  ———-  —  ———–  —–  ———-  ——-  ———  ——–
  Volume 0     E                       DVD-ROM         0 B  No Media
  Volume 1     G                       DVD-ROM         0 B  No Media
  Volume 2         System Rese  NTFS   Partition    100 MB  Healthy    System
  Volume 3     C                NTFS   Partition     68 GB  Healthy    Boot
  Volume 4     D   DATA         NTFS   Partition     80 GB  Healthy
  Volume 5     F   READYBOOST   FAT    Removable   3812 MB  Healthy
  Volume 6     H                       Removable       0 B  No Media
  Volume 7     I                       Removable       0 B  No Media
  Volume 8     K                NTFS   Removable     14 GB  Healthy

If you already copied your image into disk, you can mark MBR by using special utility called BootSect.exe shipped with WAIK. In our case (with Windows 7 embedded), you’ll have to update master boot code to use BOOTMGR (Vista and up) rather than NTLDR (XP and down)

BOOTSECT.EXE /NT60 K: /mbr

We done, have a good day and be good people. Additional information regarding USB core guys from MS can be archived from their brand new blog (hope it will be up to date).

At the end, just you to know how are CDs make by Discovery Channel

Brilliant, yet simple technical questions, can be used for work interview

Few days ago, a friend of mine, sent me set of demonstration questions for 11th grade computer science students from Russia. I throw eye on this exam and decided to use some of those questions to test candidates in my company for hardware engineer position (we’re looking for). If you’re good candidate for this position, you should be able to answer all those questions without even think a minute.

image

Following those questions:

Automatic system recoded informational message in Russian from Unicode (16-bit) into KOI-8 (8 bit). After recoding the length of the message decreased by 480 bits. What was the length of the original message in characters?
1) 30          2) 60          3) 120          4) 480

There are 119 participants in bicycle race. Special designed system registers cyclers passing waypoint by writing his serial number with minimum possible number of bits. What will be the size of temporary message, recorded by the system after 70 cyclers passed the waypoint?
1) 70 bits          2) 70 bytes          3) 490 bits          4) 119 bytes

а=D716, b=3318. What the value of c if a<c<b?
1) 11011001          2) 11011100          3) 11010111          4) 11011000

Please calculate 438+5616?
1) 1218 2) 1718 3) 6916 4) 10000012

What logical expression equals to: & !( !B | C ), while &: logical AND, !: logical NOT and |:logical OR
1) !A | !B | !C          2) A & !B & !C          3) A & B & !C          4) A & !B &C

F is a result of logical expression execution with arguments A, B and C

A B C F
1 0 0 1
0 0 0 1
1 1 1 0

What is the expression for F?
1) !A & !B & !C          2) A & B & C         3) A | B | C         4) !A | !B | !C

For encoding characters A, B, C and D we decided to use two bits numbers sequence (from 00 to 11). What was the result of coding sequence BACD on base 16?
1) 4A          2) 411          3) BACD          4) 1023

Led table contains of number of leds. Each led can be in one of following states: “On”, “Off”, “Blinking”. What is the minimal number of leds should be on table in order to display 18 difference signals?

Please calculate 2514 + 11-4 – 2110?

To calculator there are only two commands 1: “add 3”, 2: “multiply by 4”. Please write down the sequence of the calculator command in order to convert number 3 to number 57 while using not more, then 6 commands. For example to convert 2 into 50 you should write following: “21211” (X4,+3,X4,+3,+3)

The speed of data transfer over network is 128000 bits/sec. Please calculate the time (in seconds) will take to transfer 625 kBytes in this network.

Write a simple program (by using your favorite programming language) to convert the array of 30 integers into array of modulus of items from input array without using modulus calculation special function.


Good luck!

For people, currently employed in my company (hardware devs) it took less, then10 minutes to solve all those questions. However, I was shocked, that at least half of new candidates, come to interview failed to solve it.

Windows 7 – dry run or why Intel does not like Microsoft

Finally, I got a couple of free minutes to install Windows 7 x64 on my work machine. I have to admin, that installation was extremely fast. I just put DVD and keep talking with one of our architects near whiteboard. We enough to close only one issue, while Windows was installed and running. 

Windows 7 beta fish

There are number of visual glitches, but it’s beta after all. Next thing is to install drivers. Everything was great (it even find and install fingerprint reader), except three strange drivers on my Lenovo W500, that Windows 7 refused to find:

  • PCI Serial Port
  • PCI Simple Communications Controller
  • SM Bus Controller

What can be those drivers? The clue was in SM Bus. It something related to board chipset. So, just checked Intel AMT, Intel PM45 and Intel LMS. My approximation was right, Windows 7 (and Microsoft Windows Update service) has no information regarding Intel stuff while all other (more rare drivers were installed fluently). I also need to install three of those drivers in Windows Vista compatibility mode in order to make it works.

Why this happen? Why it’s so hard to two huge conglomerates to work together in order to bring customers better installation experience? It seemed, that Lenovo did huge work with Microsoft to adopt its hardware drivers for Windows 7. Even switchable graphic cards worked perfect without additional drivers. So why Lenovo can, while Intel cannot?

I believe, that the problem is with Intel, who self fighting not to loss high end (and cost) processors, when the market requests low cost Atom-based machines with low power, yet good performing graphic processors.

Regarding biometric devices and switchable graphics. If you want to be able to login to domain with your fingerprint, be sure, that you visited Control Panel\Hardware and Sound\Biometric Devices\Change settings and check this option. By default it’s off.

Login to domain with fingerprint on Windows 7

Also it not seemed, that Windows 7 supports hybrid graphic cards. So unless I’ll find a way to get rid of this issue, I have to use Lenovo switchable graphics utility…

Next thing was to install gentlemen’s assembly:

  • Windows Live Essentials
  • Microsoft Office 2007 + SPs
  • Total Commander
  • Skype
  • Virtual PC 2008 SP1
  • Visual Studio 2008 + SP1

All installations were fine, except small problem with business edition of Skype. Newly introduced Action Center advised me to install 4.0 beta version of Skype to prevent compatibility issues.

Windows 7 Action Center

Another problem (not really Windows, but Microsoft related) is a time, that takes to install Visual Studio. For some reason installation of Office (which is not smaller, then VS) takes about 5 minutes on my machine, when for Visual Studio it takes more, then 40 minutes to be installed.Devdiv guys, please do something with this installer.

So two restarts and here we go. Windows 7 with all necessary software installed. Now issues started.

The only disappointment was because of Windows Experience Index (140GB 7200RPM hard disk is not big and fast enough for this version of OS). If this not, what is?

image

But it can be fixed easily by disabling cache on disk Device Manager –> Disk Drives –> Uncheck “Disk write caching policy”.

image

After doing this your system will run much faster and score will be increased.

image

I cannot understand the reason it checked by default for non solid state drives, which unsuitable for fast caching.

Switchable graphic cards are also seemed, to be an issue for Windows 7. It does not supported by now to switch graphic cards. All you can do is to pitch BIOS for denying OS from display driver detection and set the card you want. Here how my machine scores looks like with second graphic card enabled.

image

Taskbar is for fun, not for work

First of all new branded taskbar. How I supposed to distinct what running and what don’t? Shinny borders and backgrounds? Cool, but not really helpful for me. Just for test try to set “Hide when minimized” option for Outlook 2007 and then detect whether it running or not. Ah, yes, I have to go via tinny arrow to configure taskbar icons – too bad.

Another issue with this bar is configuration of shortcuts. Just in case, sometimes I want to run programs minimized or maximized or set start keys (right click on shortcut and Properties). How should I do it for pinned items?

 image

I understand, that for 5 years Microsoft tried somehow to prevent from developers to junk users notification area on taskbar, by hiding, swapping and moving things there. Finally, they managed to solve it by putting all trash into big shiny icons? Just take a look on Microsoft Messenger appearance in tasksbar. You have to run it in Vista compatibility mode in order to prevent it putting icon in this area? Too bad! This not called “feature complete” for sure.

Software compatibility

Yes, not everything perfect. For example drag and drop (DragonDrop) for Virtual PC will not work in regular mode in Windows 7. You have to create shared folder to workaround it. All programs, required elevated permission (those which triggered UAC on Vista all the time) will ask you for run only once, but if you accidently pressed anything else, then Allow, you’ll never be able to run it again (unless resetting UAC credentials in Windows). Also all those great programs, not required installations or any registry changes (for example Notepad++ or WinSCP will always treated as suspicious by Windows Defender, thus will start very slow all the time and some times required to be sent “for inspection”. This is general fail of Defender. Why me (as user) should wait you (as service provider) to check something. Do it on background, if you want to…

My first BSoD

Yes, I did it. I never was able to get Blue Screen of Death on Windows Vista. Here it happened after another restart. The reason was very funny: I did not closed Outlook before restarted Windows 7 (this fade screen with your applications will be forcibly closed). Yes, it was not really major BSoD. Windows made memory dump to prevent future crashes and send information. BTW, if you want to be able to report problems, you should use keys from Connect website, rather, then from MSDN. Beta reporting services is sensitive to product key.

Background services or what is really missing

My overall experience of Windows 7 is 7 of 10. It is major improvement of Windows Vista, but it still far from being perfect. I would advice to add OS foreground task dispatching. There are too many developers (also Microsoft’s, who trying to perform long tasks in UI thread). I think, that operation system should handle such cases and dispatch jobs to background to prevent UI freeze.

image

Another thing, is to find good compromise between annoying user and smart operations. Other words, do not try to assume what customer want to do, let him to decide (or at least enable such option). Good example for this is very odd Clear Type wizard. What would you answer to such question?

Click whatever it looks the same, just fix it!

Why I need to see the same stuff three times, when all I want it file manager?

You have a lot of documents, misics, pictures and videos

Stop trying to replace software. You already screwed with Microsoft Valet.

image

ISO file burn directly from Windows is really great feature, but an ability to mount iso images as virtual disks would be much better.

Other words, if you, Microsoft, want to create great operation system – do operation system, do not try to complete OS issues with another nice to have features. But no words – great work. Thank you.

Ah, and do not forget to fire everybody in IE dev team… It awful and has no future.

Next time we’ll speak about developer point of view to Windows 7.

Microsoft Tag vs. QR tag

One of Microsoft’s announcements on CES09 was Microsoft Tag. Do you remember last try of Microsoft to create mobile barcodes on Live platform? I remember it. So why Windows Live Barcode was deprecated, while Microsoft creates new one? What the key difference between Quick Response approach, barely adopted by industry and new colorful MS stuff? Let’s try to understand differences and approximate future fail or success on this technology.

QR vs. Microsoft tag

Key differences:

  • The only information contains inside MS Tag is ID, which should be used to fetch all relevant information from tag server while QR contains all necessary information and can be used offline. Both technologies have it own advantages and disadvantages. From one hand, you can manage and fix results all the time, from the other hand, what happen with fraud and offline usage?
  • MS Tag using High Capacity Color Barcode (HCCB) technology, which makes able to encode more information into relatively small area. Also because of small amount of information, errors can be handled easily for MS Tag. For encode 1 byte we need 8 symbols in QR codes, while in MS Tag only 4.
  • MS Tag using thee base colors (CMYK) while QR only two (BW), thus in offset print MS Tag is much more sensitive to the quality of color plates. From other hand, if I my approximation is right, MS Tag can be printed in gray palette too, due to the fact, that it uses color differences, rather then color codes for decoding. Also it seemed, that hardware used for four barcodes will be more expensive, than similar hardware for two colors.
  • Because of the fact, that QR is mature technology (first ISS was presented in October 1997), there are huge amount of devices, supports it natively, while MS Tag is rather new. From other hand, http://gettag.mobi/ provides WM,J2ME, IPhone, Blackberry and Symbian native clients for reading MS Tags. So it seemed, that very soon (if Microsoft will not abandon it again) this will be distributed de-facto.
  • QR Code® is registered trademark of Denso Wave Inc, which makes this technology problematic for future enhancements, also HCCB, used for MS Tag is licensed by Microsoft, however as far as I understand from their IPL site, it can be used as far as remains under MS patent agreement. But maybe an appliance of this technology is protected.

Bottom line – I’m very skeptic with MS Tags, however let’s give it a chance and see whether MS Tag will become another Semacode, ShotCode, PDF417, Dot Code, Aztec Code, etc. or become Barcode technology we see every day everywhere…

Meanwhile, you can create your own MS Tag, download free reader for your mobile device from http://gettag.mobi and decide whether you like it or not. For me, this technology is cool, but the code itself is very ugly :)

Be good people and have a nice day.

Audio CD operation including CD-Text reading in pure C#

Recently we spoke about reading radio data in C#, however as in any vehicle we have also CD players. So what can be better, than to have an ability to play CDs while being notified about track name, gathered from CD-Text?

image

So, let’s start. First of all, I want to express my pain with MSDN documentation about CD-ROM structure. Documentation team, please, please, please update it. First of all it is no accurate, then there are a ton of things missing. However, “À la guerre comme à la guerre”, thus I invested three days in deep DDK research.

Before we can do anything with CD-ROM, we have to find it. I took the same approach as I used for HID devices. Let’s create a device

[SecurityPermission(SecurityAction.InheritanceDemand, UnmanagedCode = true)]
[SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
public class CDDADevice : SafeHandleZeroOrMinusOneIsInvalid, IDisposable, INotifyPropertyChanged {

Internal constructor for security reasons

[SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
internal CDDADevice(char drive) : base(true) {
   findDevice(drive);
}

And a find method itself

[SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
private void findDevice(char drive) {
   if (Drive == drive) return;
   if (Native.GetDriveType(string.Concat(drive, ":\")) == Native.DRIVE.CDROM) {
      this.handle = Native.CreateFile(string.Concat("\\.\", drive, ‘:’), Native.GENERIC_READ, Native.FILE_SHARE_READ, IntPtr.Zero, Native.OPEN_EXISTING, Native.FILE_ATTRIBUTE_READONLY | Native.FILE_FLAG_SEQUENTIAL_SCAN, IntPtr.Zero);
      if (this.handle.ToInt32() != -1 && this.handle.ToInt32() != 0) this.Drive = drive;
   }
}

Where GetDriveType and CreateFile are win32 methods with following signatures

[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
[DllImport("kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
internal static extern DRIVE GetDriveType(string drive);
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
[DllImport("kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
internal static extern IntPtr CreateFile(
      string lpFileName,
      uint dwDesiredAccess,
      uint dwShareMode,
      IntPtr SecurityAttributes,
      uint dwCreationDisposition,
      uint dwFlagsAndAttributes,
      IntPtr hTemplateFile);
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
[DllImport("kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
internal static extern bool CloseHandle(IntPtr hHandle);

Also, we need some constants

internal enum DRIVE : byte {
   UNKNOWN = 0,
   NO_ROOT_DIR,
   REMOVABLE,
   FIXED,
   REMOTE,
   CDROM,
   RAMDISK
}

internal const uint GENERIC_READ = 0x80000000;
internal const uint FILE_SHARE_READ = 0x00000001;
internal const uint OPEN_EXISTING = 3;
internal const uint FILE_ATTRIBUTE_READONLY = 0x00000001;
internal const uint FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000;

Now, when we have our cdrom handle in hands, we can read it’s Table Of Content. Now, thing become harder because of the fact, that we have to use very complicated platform method:

[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
[DllImport("kernel32.dll", EntryPoint = "DeviceIoControl", SetLastError=true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool DeviceIoControl(
   [In] IntPtr hDevice,
   IOCTL dwIoControl,
   [In] IntPtr lpInBuffer,
   uint nInBufferSize,
   IntPtr lpOutBuffer,
   uint nOutBufferSize,
   out uint lpBytesReturned,
   IntPtr lpOverlapped);

When thing are generic it’s good, however this one is, probably, most generic method in Win32 API. You can do anything with this method and you never know what to expect in lpOutBuffer :)

However, as I told earlier, I invested three days in investigations and researches (tnx to DDK documentation team) and now things become to be clearer. We need to get CDROM_TOC. It done by invoking IOCTL_CDROM_READ_TOC call

uint bytesRead = 0;
TOC = new Native.CDROM_TOC();
TOC.Length = (ushort)Marshal.SizeOf(TOC);
var hTOC = Marshal.AllocHGlobal(TOC.Length);
Marshal.StructureToPtr(TOC, hTOC, true);
if (Native.DeviceIoControl(this.handle, Native.IOCTL.CDROM_READ_TOC, IntPtr.Zero, 0, hTOC, TOC.Length, out bytesRead, IntPtr.Zero)) Marshal.PtrToStructure(hTOC, TOC);
Marshal.FreeHGlobal(hTOC);

But, not too fast. CDROM_TOC contains array of TRACK_DATA with unknown size.

typedef struct _CDROM_TOC {
  UCHAR  Length[2];
  UCHAR  FirstTrack;
  UCHAR  LastTrack;
  TRACK_DATA  TrackData[MAXIMUM_NUMBER_TRACKS];
} CDROM_TOC, *PCDROM_TOC;
typedef struct _TRACK_DATA {
  UCHAR  Reserved;
  UCHAR  Control : 4;
  UCHAR  Adr : 4;
  UCHAR  TrackNumber;
  UCHAR  Reserved1;
  UCHAR  Address[4];
} TRACK_DATA, *PTRACK_DATA;

P/Invoke it! But how to marshal unknown array? We should create wrapper object. Also there is very fun BitVector, used in this structure! What’s the problem? Pin it with some Math!

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]

public class CDROM_TOC {

   public ushort Length;

   public byte FirstTrack;

   public byte LastTrack;

   public TRACK_DATA_ARRAY TrackData;

}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]

public struct TRACK_DATA {

   public byte Reserved;

   public byte bitVector;

   public byte Control {

      get { return ((byte)((this.bitVector & 15u))); }

      set { this.bitVector = ((byte)((value | this.bitVector))); }

   }

   public byte Adr {

      get { return ((byte)(((this.bitVector & 240u) / 16))); }

      set { this.bitVector = ((byte)(((value * 16) | this.bitVector))); }

   }

   public byte TrackNumber;

   public byte Reserved1;

   public uint Address;

}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]

internal sealed class TRACK_DATA_ARRAY {

   internal TRACK_DATA_ARRAY() { data = new byte[MAXIMUM_NUMBER_TRACKS * Marshal.SizeOf(typeof(TRACK_DATA))]; }

   [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAXIMUM_NUMBER_TRACKS * 8)]

   private byte[] data;

   public TRACK_DATA this[int idx] {

      get {

         if ((idx < 0) | (idx >= MAXIMUM_NUMBER_TRACKS)) throw new IndexOutOfRangeException();

         TRACK_DATA res;

         var hData = GCHandle.Alloc(data, GCHandleType.Pinned);

         try {

            var buffer = hData.AddrOfPinnedObject();

            buffer = (IntPtr)(buffer.ToInt32() + (idx * Marshal.SizeOf(typeof(TRACK_DATA))));

            res = (TRACK_DATA)Marshal.PtrToStructure(buffer, typeof(TRACK_DATA));

         } finally {

            hData.Free();

         }

         return res;

      }

   }

}

Fuf, done. The code is rather self explaining, we just “tell” marshaler, that we have byte array, while calculating pointers to pinned object to get actual value and marshal it back. So, now we have TOC. So, we know how many tracks we have and addresses to data chunks inside the CD.

But it now enough to understand where our tracks. CD-ROM structure is very tricky. There we have blocks or sectors (which is the smallest chunks of data), so we have to convert bytes into sector addresses. Each block is 2352 bytes in RAW mode, while address value inside TRACK_DATA points us to layout address with is sync, sector id, error detection etc… So, in order to convert TRACK object into actual track number on disk, we have to stick to following method

public static int SectorAddress(this TRACK_DATA data) {

   var addr = BitConverter.GetBytes(data.Address);

   return (addr[1] * 60 * 75 + addr[2] * 75 + addr[3]) – 150;

}

Now, when we know numbers of tracks, we also know start and end sector, disk type and other useful information we are ready to twist it a bit and read CD-Text (if there are and your CD reader supports it).

So, coming back to our favorite method DeviceIoControl, but this time with IOCTL_CDROM_READ_TOC_EX control.

bytesRead = 0;           
TOCex = new Native.CDROM_READ_TOC_EX {

   Format = Native.CDROM_READ_TOC_EX_FORMAT.CDTEXT

};

var sTOCex = Marshal.SizeOf(TOCex);

var hTOCex = Marshal.AllocHGlobal(sTOCex);

Marshal.StructureToPtr(TOCex, hTOCex, true);

var Data = new Native.CDROM_TOC_CD_TEXT_DATA();

Data.Length = (ushort)Marshal.SizeOf(Data);

var hData = Marshal.AllocHGlobal(Data.Length);

Marshal.StructureToPtr(Data, hData, true);

if (Native.DeviceIoControl(this.handle, Native.IOCTL.CDROM_READ_TOC_EX, hTOCex, (ushort)sTOCex, hData, Data.Length, out bytesRead, IntPtr.Zero)) Marshal.PtrToStructure(hData, Data);

Marshal.FreeHGlobal(hData);

Marshal.FreeHGlobal(hTOCex);

Looks too simple? Let’s see inside CDROM_READ_TOC_EX structure. It is very similar to _CDROM_TOC.

typedef struct _CDROM_READ_TOC_EX {
  UCHAR Format : 4;
  UCHAR Reserved1 : 3;
  UCHAR Msf : 1;
  UCHAR SessionTrack;
  UCHAR Reserved2;
  UCHAR Reserved3;
} CDROM_READ_TOC_EX, *PCDROM_READ_TOC_EX;

Simple. Isn’t it?

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]

public struct CDROM_READ_TOC_EX {

   public uint bitVector;

   public CDROM_READ_TOC_EX_FORMAT Format {

      get { return ((CDROM_READ_TOC_EX_FORMAT)((this.bitVector & 15u))); }

      set { this.bitVector = (uint)((byte)value | this.bitVector); }

   }

   public uint Reserved1 {

      get { return ((uint)(((this.bitVector & 112u) / 16))); }

      set { this.bitVector = ((uint)(((value * 16) | this.bitVector))); }

   }

   public uint Msf {

      get { return ((uint)(((this.bitVector & 128u) / 128))); }

      set { this.bitVector = ((uint)(((value * 128) | this.bitVector))); }

   }

   public byte SessionTrack;

   public byte Reserved2;

   public byte Reserved3;

}

But what will come inside lpOutBuffer? Fellow structure, named CDROM_TOC_CD_TEXT_DATA with unknown size array of CDROM_TOC_CD_TEXT_DATA_BLOCK

typedef struct _CDROM_TOC_CD_TEXT_DATA {
  UCHAR  Length[2];
  UCHAR  Reserved1;
  UCHAR  Reserved2;
  CDROM_TOC_CD_TEXT_DATA_BLOCK  Descriptors[0];
} CDROM_TOC_CD_TEXT_DATA, *PCDROM_TOC_CD_TEXT_DATA;
typedef struct _CDROM_TOC_CD_TEXT_DATA_BLOCK {
  UCHAR  PackType;
  UCHAR  TrackNumber:7;
  UCHAR  ExtensionFlag:1;
  UCHAR  SequenceNumber;
  UCHAR  CharacterPosition:4;
  UCHAR  BlockNumber:3;
  UCHAR  Unicode:1;
  union {
    UCHAR  Text[12];
    WCHAR  WText[6];
  };
  UCHAR  CRC[2];
} CDROM_TOC_CD_TEXT_DATA_BLOCK, *PCDROM_TOC_CD_TEXT_DATA_BLOCK;

Too bad to be true. Isn’t it? Let’s try to marshal it my hands (with the trick used for TRACK_DATA

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]

public class CDROM_TOC_CD_TEXT_DATA {

   public ushort Length;

   public byte Reserved1;

   public byte Reserved2;

   public CDROM_TOC_CD_TEXT_DATA_BLOCK_ARRAY Descriptors;

}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]

internal sealed class CDROM_TOC_CD_TEXT_DATA_BLOCK_ARRAY {

   internal CDROM_TOC_CD_TEXT_DATA_BLOCK_ARRAY() { data = new byte[MINIMUM_CDROM_READ_TOC_EX_SIZE * MAXIMUM_NUMBER_TRACKS * Marshal.SizeOf(typeof(CDROM_TOC_CD_TEXT_DATA_BLOCK))]; }

   [MarshalAs(UnmanagedType.ByValArray, SizeConst = MINIMUM_CDROM_READ_TOC_EX_SIZE * MAXIMUM_NUMBER_TRACKS * 18)]

   private byte[] data;

   public CDROM_TOC_CD_TEXT_DATA_BLOCK this[int idx] {

      get {

         if ((idx < 0) | (idx >= MINIMUM_CDROM_READ_TOC_EX_SIZE * MAXIMUM_NUMBER_TRACKS)) throw new IndexOutOfRangeException();

         CDROM_TOC_CD_TEXT_DATA_BLOCK res;

         var hData = GCHandle.Alloc(data, GCHandleType.Pinned);

         try {

            var buffer = hData.AddrOfPinnedObject();

            buffer = (IntPtr)(buffer.ToInt32() + (idx * Marshal.SizeOf(typeof(CDROM_TOC_CD_TEXT_DATA_BLOCK))));

            res = (CDROM_TOC_CD_TEXT_DATA_BLOCK)Marshal.PtrToStructure(buffer, typeof(CDROM_TOC_CD_TEXT_DATA_BLOCK));

         } finally {

            hData.Free();

         }

         return res;

      }

   }

}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]

public struct CDROM_TOC_CD_TEXT_DATA_BLOCK {

   public CDROM_CD_TEXT_PACK PackType;

   public byte bitVector1;

   public byte TrackNumber {

      get { return ((byte)((this.bitVector1 & 127u))); }

      set { this.bitVector1 = ((byte)((value | this.bitVector1))); }

   }

   public byte ExtensionFlag {

      get { return ((byte)(((this.bitVector1 & 128u) / 128))); }

      set { this.bitVector1 = ((byte)(((value * 128) | this.bitVector1))); }

   }

   public byte SequenceNumber;

   public byte bitVector2;        

   public byte CharacterPosition {

      get { return ((byte)((this.bitVector2 & 15u))); }

      set { this.bitVector2 = ((byte)((value | this.bitVector2))); }

   }

   public byte BlockNumber {

      get { return ((byte)(((this.bitVector2 & 112u) / 16))); }

      set { this.bitVector2 = ((byte)(((value * 16) | this.bitVector2))); }

   }

   public byte Unicode {

      get { return ((byte)(((this.bitVector2 & 128u) / 128))); }

      set { this.bitVector2 = ((byte)(((value * 128) | this.bitVector2))); }

   }

   [MarshalAs(UnmanagedType.ByValArray, SizeConst = 12, ArraySubType = UnmanagedType.I1)]

   public byte[] TextBuffer;

   public string Text {

      get { return (Unicode == 1) ? ASCIIEncoding.ASCII.GetString(TextBuffer) : UTF32Encoding.UTF8.GetString(TextBuffer); }

   }

   public ushort CRC;

}

Can’t you see a small problem here? Yes, we do not know the actual/maximum size of CDROM_TOC_CD_TEXT_DATA_BLOCK array. Until, I’ll find a nice way to marshal smart pointers, we’ll stick to MAX_TRACKS (100) * MIN_DATA_BLOCK (2).

We almost finished and the worst things are behind us. Now you should enumerate thru CDROM_TOC_CD_TEXT_DATA_BLOCK and look for Text, TrackNumber and SequenceNumber (which is continuation of text reported). For example, slot for ALBUM_NAME “Satisfaction” will looks as following

BlockNumber 0x00
CharacterPosition 0x00
CRC 0x3EAB
SequenceNumber 0x00
Text SATISFACTIO
BlockNumber 0x00
CharacterPosition 0x0B
CRC 0x0564
SequenceNumber 0x01
Text N

 

And so on… What to do with all other data and how to use it to enhance listening (ripping/crunching/seeking) experience we’ll speak next time. Have a good day and be good people.

USB FM radio library was published on CodePlex

I just published a part of my latest project – dynamic library to work with FM receivers on CodePlex under MS-PL. So, feel free do download, test and use it.

Note, that this release is preliminary and has a lot of bugs. Also, RDS is not fully implements as well as recording capabilities with Direct Sound.

I’m keep working to provide WPF UI for this library to “productize” it.

So, what are you waiting for? Download and Spear the word with this news! This is the first and only fully managed library (as far as I know) to work with RDS, TMC and FM data. Also, there are not a lot of information about HID usage as FM receiver in managed code.

image

Download latest release of USBFM.DLL >>

Read and use FM radio (or any other USB HID device) from C#

Last time we spoke about reading and decoding RDS information from FM receivers. Also we already know how to stream sound from DirectSound compatible devices. However, before we can do it, we should be able to “speak” with such devices. So, today we’ll spoke about detection and reading information from Radio USB adapters (actually from any Human Input Devices). Let’s start.

USB FM HID

First, if you want to do it, go and buy such device. The are not a lot of alternatives, but if you’ll seek, you’ll find it very quickly.

So, let’s start. First of all, we’ll use platform invoke to get and set the information. Also, we have to preserve handle of the device from being collected by GC. After we’ll finish using the device, we’ll have to dispose it. Thus it makes sense to inherit from SafeHandle and IDisposable.

[SecurityPermission(SecurityAction.InheritanceDemand, UnmanagedCode = true)]
[SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
public class USBDevice : SafeHandleZeroOrMinusOneIsInvalid, IDisposable {

Next, we’ll set a number of arguments, that will be in use during the device lifetime.

public uint ProductID { get; private set; }
public uint VendorID { get; private set; }
public uint VersionNumber { get; private set; }
public string Name { get; private set; }
public string SerialNumber { get; private set; }
public override bool IsInvalid { get { return !isValid; } }

internal ushort FeatureReportLength { get; private set; }
internal ushort[] Registers { get; set; }

Now, we have to find it. The best way of detection human input devices is by product and vendor IDs. Those values are always unique for certain device type.

[SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
internal USBDevice(uint pid, uint vid) : base(true) { findDevice(pid, vid); }

Next step is to find a device. To do this, we have to provide extern interfaces to methods of hid.dll and setupapi.dll. Here all methods we will use in our class

[SuppressUnmanagedCodeSecurity()]
internal static class Native {
   #region methods
   [DllImport("hid.dll", SetLastError = true)]
   internal static extern void HidD_GetHidGuid(
      ref Guid lpHidGuid);

   [DllImport("hid.dll", SetLastError = true)]
   internal static extern bool HidD_GetAttributes(
      IntPtr hDevice,
      out HIDD_ATTRIBUTES Attributes);

   [DllImport("hid.dll", SetLastError = true)]
   internal static extern bool HidD_GetPreparsedData(
      IntPtr hDevice,
      out IntPtr hData);

   [DllImport("hid.dll", SetLastError = true)]
   internal static extern bool HidD_FreePreparsedData(
      IntPtr hData);

   [DllImport("hid.dll", SetLastError = true)]
   internal static extern bool HidP_GetCaps(
      IntPtr hData,
      out HIDP_CAPS capabilities);

   [DllImport("hid.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
   internal static extern bool HidD_GetFeature(
      IntPtr hDevice,
      IntPtr hReportBuffer,
      uint ReportBufferLength);

   [DllImport("hid.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
   internal static extern bool HidD_SetFeature(
      IntPtr hDevice,
      IntPtr ReportBuffer,
      uint ReportBufferLength);

   [DllImport("hid.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
   internal static extern bool HidD_GetProductString(
      IntPtr hDevice,
      IntPtr Buffer,
      uint BufferLength);

   [DllImport("hid.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
   internal static extern bool HidD_GetSerialNumberString(
      IntPtr hDevice,
      IntPtr Buffer,
      uint BufferLength);

   [DllImport("setupapi.dll", SetLastError = true)]
   internal static extern IntPtr SetupDiGetClassDevs(
      ref Guid ClassGuid,
      [MarshalAs(UnmanagedType.LPTStr)] string Enumerator,
      IntPtr hwndParent,
      UInt32 Flags);

   [DllImport("setupapi.dll", SetLastError = true)]
   internal static extern bool SetupDiEnumDeviceInterfaces(
      IntPtr DeviceInfoSet,
      int DeviceInfoData,
      ref  Guid lpHidGuid,
      uint MemberIndex,
      ref  SP_DEVICE_INTERFACE_DATA lpDeviceInterfaceData);

   [DllImport("setupapi.dll", SetLastError = true)]
   internal static extern bool SetupDiGetDeviceInterfaceDetail(
      IntPtr DeviceInfoSet,
      ref SP_DEVICE_INTERFACE_DATA lpDeviceInterfaceData,
      IntPtr hDeviceInterfaceDetailData,
      uint detailSize,
      out uint requiredSize,
      IntPtr hDeviceInfoData);

   [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
   [DllImport("kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
   internal static extern IntPtr CreateFile(
         string lpFileName,
         uint dwDesiredAccess,
         uint dwShareMode,
         IntPtr SecurityAttributes,
         uint dwCreationDisposition,
         uint dwFlagsAndAttributes,
         IntPtr hTemplateFile);

   [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
   [DllImport("kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
   internal static extern bool CloseHandle(IntPtr hHandle);

Also, we will need a number of structures, such as device attributes and capabilities.

[StructLayout(LayoutKind.Sequential)]
internal struct SP_DEVICE_INTERFACE_DATA {
   public int cbSize;
   public Guid InterfaceClassGuid;
   public int Flags;
   public int Reserved;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
internal class PSP_DEVICE_INTERFACE_DETAIL_DATA {
   public int cbSize;
   [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
   public string DevicePath;
}

[StructLayout(LayoutKind.Sequential)]
internal struct HIDD_ATTRIBUTES {
   public int Size; // = sizeof (struct _HIDD_ATTRIBUTES) = 10
   public UInt16 VendorID;
   public UInt16 ProductID;
   public UInt16 VersionNumber;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HIDP_CAPS {
   public UInt16 Usage;
   public UInt16 UsagePage;
   public UInt16 InputReportByteLength;
   public UInt16 OutputReportByteLength;
   public UInt16 FeatureReportByteLength;
   [MarshalAs(UnmanagedType.ByValArray, SizeConst = 17)]
   public UInt16[] Reserved;
   public UInt16 NumberLinkCollectionNodes;
   public UInt16 NumberInputButtonCaps;
   public UInt16 NumberInputValueCaps;
   public UInt16 NumberInputDataIndices;
   public UInt16 NumberOutputButtonCaps;
   public UInt16 NumberOutputValueCaps;
   public UInt16 NumberOutputDataIndices;
   public UInt16 NumberFeatureButtonCaps;
   public UInt16 NumberFeatureValueCaps;
   public UInt16 NumberFeatureDataIndices;
}

And a number of system constants

internal const uint DIGCF_PRESENT = 0x00000002;
internal const uint DIGCF_DEVICEINTERFACE = 0x00000010;
internal const uint GENERIC_READ = 0x80000000;
internal const uint GENERIC_WRITE = 0x40000000;
internal const uint FILE_SHARE_READ = 0x00000001;
internal const uint FILE_SHARE_WRITE = 0x00000002;
internal const int OPEN_EXISTING = 3;
internal const int FILE_FLAG_OVERLAPPED = 0x40000000;
internal const uint MAX_USB_DEVICES = 16;

Now, we are ready to start. So let’s find all devices and get its information

Native.HidD_GetHidGuid(ref _hidGuid);
hHidDeviceInfo = Native.SetupDiGetClassDevs(ref _hidGuid, null, IntPtr.Zero, Native.DIGCF_PRESENT | Native.DIGCF_DEVICEINTERFACE);

Now, if a handle we get is valid, we should search our specific device. For this purpose, we have to read device interface information and then get details info about this device.

if (hHidDeviceInfo.ToInt32() > -1) {
   uint i = 0;
   while (!isValid && i < Native.MAX_USB_DEVICES) {
      var hidDeviceInterfaceData = new Native.SP_DEVICE_INTERFACE_DATA();
      hidDeviceInterfaceData.cbSize = Marshal.SizeOf(hidDeviceInterfaceData);
      if (Native.SetupDiEnumDeviceInterfaces(hHidDeviceInfo, 0, ref _hidGuid, i, ref hidDeviceInterfaceData)) {

Once we have all this and information is valid, let’s detect its capabilities

bool detailResult;
uint length, required;
Native.SetupDiGetDeviceInterfaceDetail(hHidDeviceInfo, ref hidDeviceInterfaceData, IntPtr.Zero, 0, out length, IntPtr.Zero);
var hidDeviceInterfaceDetailData = new Native.PSP_DEVICE_INTERFACE_DETAIL_DATA();
hidDeviceInterfaceDetailData.cbSize = 5; //DWORD cbSize (size 4) + Char[0] (size 1) for 32bit only!
var hDeviceInterfaceDetailData = Marshal.AllocHGlobal(Marshal.SizeOf(hidDeviceInterfaceDetailData));
Marshal.StructureToPtr(hidDeviceInterfaceDetailData, hDeviceInterfaceDetailData, true);
detailResult = Native.SetupDiGetDeviceInterfaceDetail(hHidDeviceInfo, ref hidDeviceInterfaceData, hDeviceInterfaceDetailData, length, out required, IntPtr.Zero);
Marshal.PtrToStructure(hDeviceInterfaceDetailData, hidDeviceInterfaceDetailData);
if (detailResult) {

To do this, we have to create memory file first and then share device attributes by using this file.

base.handle = Native.CreateFile(hidDeviceInterfaceDetailData.DevicePath,
                        Native.GENERIC_READ |
                        Native.GENERIC_WRITE,
                        Native.FILE_SHARE_READ |
                        Native.FILE_SHARE_WRITE,
                        IntPtr.Zero,
                        Native.OPEN_EXISTING,
                        Native.FILE_FLAG_OVERLAPPED,
                        IntPtr.Zero);
                     if (base.handle.ToInt32() > -1) {
                        Native.HIDD_ATTRIBUTES hidDeviceAttributes;
                        if (Native.HidD_GetAttributes(base.handle, out hidDeviceAttributes)) {

All the rest is straight forward. Just compare info retrieved with one we already have. And, of cause, release all resources were used (remember, we’re in win32 api world!)

if ((hidDeviceAttributes.VendorID == vid) && (hidDeviceAttributes.ProductID == pid)) {
                              isValid = true;
                              ProductID = pid;
                              VendorID = vid;
                              VersionNumber = hidDeviceAttributes.VersionNumber;
                              IntPtr buffer = Marshal.AllocHGlobal(126);//max alloc for string;
                              if (Native.HidD_GetProductString(this.handle, buffer, 126)) Name = Marshal.PtrToStringAuto(buffer);
                              if (Native.HidD_GetSerialNumberString(this.handle, buffer, 126)) SerialNumber = Marshal.PtrToStringAuto(buffer);
                              Marshal.FreeHGlobal(buffer);
                              var capabilities = new Native.HIDP_CAPS();
                              IntPtr hPreparsedData;
                              if (Native.HidD_GetPreparsedData(this.handle, out hPreparsedData)) {
                                 if (Native.HidP_GetCaps(hPreparsedData, out capabilities)) FeatureReportLength = capabilities.FeatureReportByteLength;
                                 Native.HidD_FreePreparsedData(hPreparsedData);
                              }
                              break;
                           }
                        } else {
                           Native.CloseHandle(base.handle);
                        }
                     }
                  }
                  Marshal.FreeHGlobal(hDeviceInterfaceDetailData);
               }
               i++;

            }

Now we have a handle to our device and can manipulate it. Like this:

using (var device = USBRadioDevice.FindDevice(0x0000, 0x1111)) {

}

But we still have to provide methods for such usage. Here there are no very complicated code.

public static USBDevice FindDevice(uint pid, uint vid) {
   var device = new USBDevice(pid,vid);
   var fillRegisters = device.InitRegisters();
   if (!device.IsInvalid && fillRegisters) return device;
   else throw new ArgumentOutOfRangeException(string.Format("Human input device {0} was not found.", pid));
}

public override string ToString() {
   return string.Format("{0} (Product:{1:x}, Vendor:{2:x}, Version:{3:x}, S/N:{4})", Name, ProductID, VendorID, VersionNumber, SerialNumber);
}

[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
protected override bool ReleaseHandle() {
   return Native.CloseHandle(base.handle);
}

#region IDisposable Members
public void Dispose() {
   Dispose(true);
   GC.SuppressFinalize(this);

}
[SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
void IDisposable.Dispose() {
   if (base.handle != null && !base.IsInvalid) {
      // Free the handle
      base.Dispose();
   }
}

#endregion

We done. Have a nice day and be good people.

Capturing and streaming sound by using DirectSound with C#

I already wrote a little about managed way to use DirectX DirectSound. Today we’ll speak about how to get sound from your microphone or any other DirectSound capturing device (such as FM receiver) and stream it out to your PC speakers and any other DirectSound Output device. So, let’s start creating our first echo service by using managed DirectX.

image

First of all we should decide what Wave format we want to use for capturing and recording. So, let’s choose anything reasonable :)

var format = new WaveFormat {
            SamplesPerSecond = 96000,
            BitsPerSample = 16,
            Channels = 2,
            FormatTag = WaveFormatTag.Pcm
         };

Now, we should calculate block align and average byte per second value for this format. I’m wondering why it cannot be done automatically…

format.BlockAlign = (short)(format.Channels * (format.BitsPerSample / 8));
format.AverageBytesPerSecond = format.SamplesPerSecond * format.BlockAlign;

Next step is to set the size of two buffers – one for input and other for output. Generally those buffers are circular, and capturing one should be twice bigger, then output. Why? Because we choose two channels to use. Also, we should decide about chunk size of the buffer, we want to signal when filled.

_dwNotifySize = Math.Max(4096, format.AverageBytesPerSecond / 8);
_dwNotifySize -= _dwNotifySize % format.BlockAlign;
_dwCaptureBufferSize = NUM_BUFFERS * _dwNotifySize;
_dwOutputBufferSize = NUM_BUFFERS * _dwNotifySize / 2;

Next step is to create CaptureBufferDescriptor and actual capturing buffer. We’ll enumerate all devices and choose one, satisfies given string (captureDescriptor) – for example “Mic” :)

var cap = default(Capture);
var cdc = new CaptureDevicesCollection();
for (int i = 0; i < cdc.Count; i++) {
   if (cdc[i].Description.ToLower().Contains(captureDescriptor.ToLower())) {
      cap = new Capture(cdc[i].DriverGuid);
      break;
   }
}
var capDesc = new CaptureBufferDescription {
   Format = format,
   BufferBytes = _dwCaptureBufferSize
};
_dwCapBuffer = new CaptureBuffer(capDesc, cap);

Then we’ll create output device and buffer. To simplify program, we will use default speakers to output, however, you can choose output device the same way we did for capturing. Also, because DirectSound uses any window as it’s message pump, we have to use SetCooperativeLevel method. In my case (windowless application), I’ll use desktop window as message broker. This why you will have to add Windows.Forms as reference for your project, even if it console application. Also, do not forget to set GlobalFocus value to True, if you want to play echo, even if desktop window is not focused.

var dev = new Device();
dev.SetCooperativeLevel(Native.GetDesktopWindow(), CooperativeLevel.Priority);

var devDesc = new BufferDescription {
   BufferBytes = _dwOutputBufferSize,
   Format = format,
   DeferLocation = true,
   GlobalFocus = true
};
_dwDevBuffer = new SecondaryBuffer(devDesc, dev);

Now, we will subscribe to buffer notifications and set autoResetEvent to be fired when it filled up.

var _resetEvent = new AutoResetEvent(false);
var _notify = new Notify(_dwCapBuffer);
//half&half
var bpn1 = new BufferPositionNotify();
bpn1.Offset = _dwCapBuffer.Caps.BufferBytes / 2 – 1;
bpn1.EventNotifyHandle = _resetEvent.SafeWaitHandle.DangerousGetHandle();
var bpn2 = new BufferPositionNotify();
bpn2.Offset = _dwCapBuffer.Caps.BufferBytes – 1;
bpn2.EventNotifyHandle = _resetEvent.SafeWaitHandle.DangerousGetHandle();

_notify.SetNotificationPositions(new BufferPositionNotify[] { bpn1, bpn2 });

Almost done, the only thing we should do is to fire worker thread to take care on messages

int offset = 0;
_dwCaptureThread = new Thread((ThreadStart)delegate {
   _dwCapBuffer.Start(true);

   while (IsReady) {
      _resetEvent.WaitOne();
      var read = _dwCapBuffer.Read(offset, typeof(byte), LockFlag.None, _dwOutputBufferSize);
      _dwDevBuffer.Write(0, read, LockFlag.EntireBuffer);
      offset = (offset + _dwOutputBufferSize) % _dwCaptureBufferSize;
      _dwDevBuffer.SetCurrentPosition(0);
      _dwDevBuffer.Play(0, BufferPlayFlags.Default);
   }
   _dwCapBuffer.Stop();
});
_dwCaptureThread.Start();

That’s it. Compile and run. Now if you’ll speak, you can hear your echo from PC speakers.

Merry Christmas for whom concerns and be good people – do not scare your co-workers with strange sounds – be polite and make the volume lower :)

What boots faster – Netbook, powered Windows XP or Nokia E71 mobile phone?

Some days ago, somebody from Microsoft was shocked, when I told him, that I’m planning to run Windows XP (and later Windows 7) as operation system for mission critical automotive device. He even checked with Windows XP embedded team boot times for XP. They told him, that the minimum can be achieved is about 40 seconds cold boot and 30 seconds from hibernate state. I was upset and decided to tweak my system for smallest possible boot time. Here the result video. This is not the limit. I believe, that I’ll be able to decrease Windows XP boot time to less, then 10 seconds with a bit more efforts.

Note: This is absolutely authentic and non-touched video, recorded today by me, comparing boot time of Windows XP on unbranded weak netbook (Atom 1.6, 128MB and 8G SSD) and my Nokia E71 mobile phone. 15 seconds boot time of Windows XP achieved by tweaking only well known registry values and OS configuration values without special profundity of system settings.

Now the question: with todays’ devices, why we are not running XP for mobile and automotive mission critical devices?

Reading and decoding RDS (Radio Data System) in C#

RDS or Radio Data System is very common in US and many European countries. It is communication protocol used to send small amount of digital information using regular FM radio broadcast. This protocol is used to “tell” your receiver about alternative frequencies, time, program notifications, program types, traffic information and regular text (such as singer name or genre). Unfortunately in Israel RDS is not very common and there is very limited number of radio stations broadcasts RDS information.

image

How RDS works?

As mentioned earlier, it uses FM subcarrier to broadcast digital information. It was designed to support 10 and 18 characters numeric and 80 characters alphanumeric displays. RDS operates at 1187.5 bps and based on 26-bit word consisting of 16 data and 10 error detection bits. Due to the fact, that FM carrier is not very reliable, error code allows correct information to be received even if an error of 3-5 bits exists within 26 bit block. Each four data blocks interpreted as 104-bit signal and named “group”. Depending of the type of information, contained within the group, as different group type code is defined and transmitted within the group as upper five bits code. Even if more, then 104 bits required to completely send the information, there is no requirement that the next segment of the transmission be sent in the next group. There are 32 known groups types, defined by RFC:

private enum groupType : byte {
   RDS_TYPE_0A = (0 * 2 + 0),
   RDS_TYPE_0B = (0 * 2 + 1),
   RDS_TYPE_1A = (1 * 2 + 0),
   RDS_TYPE_1B = (1 * 2 + 1),
   RDS_TYPE_2A = (2 * 2 + 0),
   RDS_TYPE_2B = (2 * 2 + 1),
   RDS_TYPE_3A = (3 * 2 + 0),
   RDS_TYPE_3B = (3 * 2 + 1),
   RDS_TYPE_4A = (4 * 2 + 0),
   RDS_TYPE_4B = (4 * 2 + 1),
   RDS_TYPE_5A = (5 * 2 + 0),
   RDS_TYPE_5B = (5 * 2 + 1),
   RDS_TYPE_6A = (6 * 2 + 0),
   RDS_TYPE_6B = (6 * 2 + 1),
   RDS_TYPE_7A = (7 * 2 + 0),
   RDS_TYPE_7B = (7 * 2 + 1),
   RDS_TYPE_8A = (8 * 2 + 0),
   RDS_TYPE_8B = (8 * 2 + 1),
   RDS_TYPE_9A = (9 * 2 + 0),
   RDS_TYPE_9B = (9 * 2 + 1),
   RDS_TYPE_10A = (10 * 2 + 0),
   RDS_TYPE_10B = (10 * 2 + 1),
   RDS_TYPE_11A = (11 * 2 + 0),
   RDS_TYPE_11B = (11 * 2 + 1),
   RDS_TYPE_12A = (12 * 2 + 0),
   RDS_TYPE_12B = (12 * 2 + 1),
   RDS_TYPE_13A = (13 * 2 + 0),
   RDS_TYPE_13B = (13 * 2 + 1),
   RDS_TYPE_14A = (14 * 2 + 0),
   RDS_TYPE_14B = (14 * 2 + 1),
   RDS_TYPE_15A = (15 * 2 + 0),
   RDS_TYPE_15B = (15 * 2 + 1)
}

Not all groups are in use all the time. However, there are some commitments, defined by the protocol. For example, 1A have to be transmitted at least once a second. This group contains special information, required for receivers to be synchronized and locked into the transmitting channel.

Within the error correction information we also receive the direction to treat them.

private enum correctedType : byte {
   NONE = 0,
   ONE_TO_TWO = 1,
   THREE_TO_FIVE = 2,
   UNCORRECTABLE = 3
}

Also, each message type has it own limits. For example RT (Radio Text – 64 character text to display on your receiver) and PS (Programme Service – eight character station identification) message are limited to 2 groups, when PI (Programme Identification – unique code of the station) and PTY (Programme Type – one of 31 predefined program types – e.g. News, Drama, Music) are limited to 4.

In addition to those constraints, block types are also different. But in this case, there are only 4 kinds

private enum blockType : byte {
   A = 6,
   B = 4,
   C = 2,
   D = 0
}

So, what we’re waiting for? Let’s start working.

Handling errors

First of all we should take care on errors and fix them if possible. For this purpose, we should first count them and detect the way of fixing

var errorCount = (byte)((registers[0xa] & 0x0E00) >> 9);
var errorFlags = (byte)(registers[0x6] & 0xFF);
if (errorCount < 4) {
   _blocksValid += (byte)(4 – errorCount);
} else { /*drop data on more errors*/ return; }

Once it done, we can try to fix them

//Also drop the data if more than two errors were corrected
if (_getErrorsCorrected(errorFlags, blockType.B) > correctedType.ONE_TO_TWO) return;

private correctedType _getErrorsCorrected(byte data, blockType block) { return (correctedType)((data >> (byte)block) & 0x30); }

Now, our registers should be fine and we can start the detection of group type

Group Type Detection

This is very simple task, all we have to do is to get five upper bites to get a type and version.

var group_type = (groupType)(registers[0xD] >> 11);

Then we can handle PI and PTY, which we always have in RDS.

PI and PTY treatment

Now, let’s update pi code, due to the fact, that B format always have PI in words A and C

_updatePI(registers[0xC]);

if (((byte)group_type & 0x01) != 0) {
_updatePI(registers[0xE]);
}

To update PI, we should check whether the new value is different from the previous and update it only in case it changed.

private void _updatePI(byte pi) {
   uint rds_pi_validate_count = 0;
   uint rds_pi_nonvalidated = 0;

   // if the pi value is the same for a certain number of times, update a validated pi variable
   if (rds_pi_nonvalidated != pi) {
      rds_pi_nonvalidated = pi;
      rds_pi_validate_count = 1;
   } else {
      rds_pi_validate_count++;
   }

   if (rds_pi_validate_count > PI_VALIDATE_LIMIT) {
      _piDisplay = rds_pi_nonvalidated;
   }
}

Then we will update PTY

_updatePTY((byte)((registers[0xd] >> 5) & 0x1f));

PTY treatment is very similar to PI, however it can be multiplied. 

private void _updatePTY(byte pty) {
   uint rds_pty_validate_count = 0;
   uint rds_pty_nonvalidated = 0;

   // if the pty value is the same for a certain number of times, update a validated pty variable
   if (rds_pty_nonvalidated != pty) {
      rds_pty_nonvalidated = pty;
      rds_pty_validate_count = 1;
   } else {
      rds_pty_validate_count++;
   }

   if (rds_pty_validate_count > PTY_VALIDATE_LIMIT) {
      _ptyDisplay = rds_pty_nonvalidated;
   }
}

When we done with those two groups, we can start handling another. Today, we’ll handle only 0B, 2A and 2B types (I have a good reason for it, due to the fact, that only those are supported in Israel by now :) ) So,

Handling PS and different RTs

Simple switch on those groups

switch (group_type) {
   case groupType.RDS_TYPE_0B:
      addr = (byte)((registers[0xd] & 0x3) * 2);
      _updatePS((byte)(addr + 0), (byte)(registers[0xf] >> 8));
      _updatePS((byte)(addr + 1), (byte)(registers[0xf] & 0xff));
      break;
   case groupType.RDS_TYPE_2A:
      addr = (byte)((registers[0xd] & 0xf) * 4);
      abflag = (byte)((registers[0xb] & 0x0010) >> 4);
      _updateRT(abflag, 4, addr, (byte[])registers.Skip(0xe), errorFlags);
      break;
   case groupType.RDS_TYPE_2B:
      addr = (byte)((registers[0xd] & 0xf) * 2);
      abflag = (byte)((registers[0xb] & 0x0010) >> 4);
      // The last 32 bytes are unused in this format
      _rtTmp0[32] = 0x0d;
      _rtTmp1[32] = 0x0d;
      _rtCnt[32] = RT_VALIDATE_LIMIT;
      _updateRT(abflag, 2, addr, (byte[])registers.Skip(0xe), errorFlags);
      break;
}

and let’s dig into PS.

In PS, we have high and low probability bits. So, if new bit in sequence matches the high probability bite and we have recieved enough bytes to max out the counter, we’ll push it into the low probability array.

if (_psTmp0[idx] == default(byte)) {
           if (_psCnt[idx] < PS_VALIDATE_LIMIT) {
               _psCnt[idx]++;
            } else {
               _psCnt[idx] = PS_VALIDATE_LIMIT;
               _psTmp1[idx] = default(byte);
            }
         }

Else, if new byte matches with the low probability byte, we should swap them and then reset the counter, by flagging the text as in transition.

else if (_psTmp1[idx] == default(byte)) {
            if (_psCnt[idx] >= PS_VALIDATE_LIMIT) {
               isTextChange = true;
            }
            _psCnt[idx] = PS_VALIDATE_LIMIT + 1;
            _psTmp1[idx] = _psTmp0[idx];
            _psTmp0[idx] = default(byte);
         }

When we have an empty byte in high probability array or new bytes does not match anything we know, we should put it into low probability array.

else if (_psCnt[idx] == null) {
            _psTmp0[idx] = default(byte);
            _psCnt[idx] = 1;
         } else {
            _psTmp1[idx] = default(byte);
         }

Now, if we marked our text as changed, we should decrement the count for all characters to prevent displaying of partical message, which in still in transition.

         if (isTextChange) {
            for (byte i = 0; i < _psCnt.Length; i++) {
               if (_psCnt[i] > 1) {
                  _psCnt[i]–;
               }
            }
         }

Then by checking PS text for incompetence, when there are characters in high probability array has been seen fewer times, that was limited by validation.

         for (byte i = 0; i < _psCnt.Length; i++) {
            if (_psCnt[i] < PS_VALIDATE_LIMIT) {
               isComplete = false;
               break;
            }
         }

Only if PS text in the high probability array is complete, we’ll copy it into display.

         if (isComplete) {
            for (byte i = 0; i < _psDisplay.Length; i++) {
               _psDisplay[i] = _psTmp0[i];
            }
         }

It is not very hard to treat PS. Isn’t it? Let’s see what’s going on with RT.

If A and B message flag changes, we’ll try to force a display by increasing the validation count for each byte. Then, we’ll wipe any cached text.

   if (abFlag != _rtFlag && _rtFlagValid) {
      // If the A/B message flag changes, try to force a display
      // by increasing the validation count of each byte
      for (i = 0; i < _rtCnt.Length; i++) _rtCnt[addr + i]++;
      _updateRTValue();

      // Wipe out the cached text
      for (i = 0; i < _rtCnt.Length; i++) {
         _rtCnt[i] = 0;
         _rtTmp0[i] = 0;
         _rtTmp1[i] = 0;
      }
   }

Now A and B flags are safe, sp we can start with message processing. First of all, NULL in RDS means space :)

   _rtFlag = abFlag;   
   _rtFlagValid = true;   

   for (i = 0; i < count; i++) {
      if (p[i] == null) p[i] = (byte)’ ‘;

The new byte matches the high probability byte also in this case. We habe to recieve this bite enough to max out counters. Then we can push it into the low probability as well.

      if (_rtTmp0[addr + i] == p[i]) {
         if (_rtCnt[addr + i] < RT_VALIDATE_LIMIT) _rtCnt[addr + i]++;
         else {
            _rtCnt[addr + i] = RT_VALIDATE_LIMIT;
            _rtTmp1[addr + i] = p[i];
         }
      }

When the new byte matches with low probability byte, we’ll swap them as well and reset counters to update text in transition flag. However in this case, our counter will go higher, then the validation limit. So we’ll have to remove it down later.

else if (_rtTmp1[addr + i] == p[i]) {

         if (_rtCnt[addr + i] >= PS_VALIDATE_LIMIT) isChange = true;

         _rtCnt[addr + i] = RT_VALIDATE_LIMIT + 1;
         _rtTmp1[addr + i] = _rtTmp0[addr + i];
         _rtTmp0[addr + i] = p[i];
      }

Now, the new byte is replaced an empty byte in the high probability array. Also, if this byte does not match anything, we should move it into low probability.

else if (_rtCnt[addr + i] == null) {
         _rtTmp0[addr + i] = p[i];
         _rtCnt[addr + i] = 1;
      } else _rtTmp1[addr + i] = p[i];

   }

Now when the text is changing, we’ll decrement the counter for all characters exactly as we did for PS.

      for (i = 0; i < _rtCnt.Length; i++) {
         if (_rtCnt[i] > 1) _rtCnt[i]–;
      }
   }

However, right after, we’ll update display. 

   _updateRTValue();
}

Displaying RT

But how to convert all those byte arrays into readable message? Simple :)

First of all if text is incomplete, we should keep loading it. Also it makes sense to check whether the target array is shorter then maximum allowed to prevent junk from being displayed.

for (i = 0; i < _rtTmp0.Length; i++) {
   if (_rtCnt[i] < RT_VALIDATE_LIMIT) {
      isComplete = false;
      break;
   }
   if (_rtTmp0[i] == 0x0d) {
      break;
   }
}

Now, when our Radio Text is in the high probability and it complete, we should copy buffers.

if (isComplete) {
   _Text = string.Empty;

   for (i = 0; i < _rtDisplay.Length; i += 2) {
      if ((_rtDisplay[i] != 0x0d) && (_rtDisplay[i + 1] != 0x0d)) {
         _rtDisplay[i] = _rtTmp0[i + 1];
         _rtDisplay[i + 1] = _rtTmp0[i];
      } else {
         _rtDisplay[i] = _rtTmp0[i];
         _rtDisplay[i + 1] = _rtTmp0[i + 1];
      }

      if (_rtDisplay[i] != 0x0d)
         _Text += _rtDisplay[i];

      if (_rtDisplay[i + 1] != 0x0d)
         _Text += _rtDisplay[i + 1];

      if ((_rtDisplay[i] == 0x0d) || (_rtDisplay[i + 1] == 0x0d))
         i = (byte)_rtDisplay.Length;
   }

And not forget to wipe out everything after the end of the message :)

   for (i++; i < _rtDisplay.Length; i++) {
      _rtDisplay[i] = 0;
      _rtCnt[i] = 0;
      _rtTmp0[i] = 0;
      _rtTmp1[i] = 0;
   }
}

And finally update the text

Text = _Text;

We done. Now we can handle RDS digital messages, but what to do with analog data we get? Don’t you already know? I blogged about it here.

Have a nice day and be good people, because you know how to write client, knows to get and parse radio data in managed code.

image

Recommended

 

Sponsor


Partners

WPF Disciples
Dreamhost
Code Project