Skip to contentSkip to conversation

Fixes AC3 output on Audigy2 sound cards.

PATCH In review James Courtier-Dutton 3 participants 10 messages Mar 19, 2005 – Mar 24, 2005

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 Fixes AC3 output on Audigy2 sound cards.

Hi,

This patch adds a DSP patch to fix an spdif_bug on some Audigy2 cards.
It also does detailed identification of the capabilities of the sound card so that the patch is only applied to the sound cards that really need it.

Signed off: James Courtier-Dutton

The Audigy2.conf file must be placed in alsa-lib. /usr/share/alsa/cards. audigy-ac3-fix.diff.txt is a patch against the current CVS.

#
# Configuration for the Audigy2 chip
#

<confdir:pcm/front.conf>

Audigy2.pcm.front.0 {
	@args [ CARD ]
	@args.CARD {
		type string
	}
	type hooks
	slave.pcm {
		type hw
		card $CARD
		device 0
	}
	hooks.0 {
		type ctl_elems
		hook_args [
			{
				name "EMU10K1 PCM Send Volume"
				index { @func private_pcm_subdevice }
				lock true
				value [ 255 255 0 0 0 0 0 0  255 0 0 0 0 0 0 0  0 255 0 0 0 0 0 0 ]
			}
						{
				name "EMU10K1 PCM Send Routing"
				index { @func private_pcm_subdevice }
				lock true
				value [ 8 9 0 0 0 0 0 0  8 9 0 0 0 0 0 0  8 9 0 0 0 0 0 0 ]
			}

		]
	}

}

<confdir:pcm/rear.conf>

Audigy2.pcm.rear.0 {
	@args [ CARD ]
	@args.CARD {
		type string
	}
	type hooks
	slave.pcm {
		type hw
		card $CARD
		device 0
	}
	hooks.0 {
		type ctl_elems
		hook_args [
			{
				name "EMU10K1 PCM Send Volume"
				index { @func private_pcm_subdevice }
				lock true
				value [ 0 0 255 255 0 0 0 0  0 0 255 0 0 0 0 0  0 0 0 255 0 0 0 0 ]
			}
		]
	}
}	

<confdir:pcm/center_lfe.conf>

Audigy2.pcm.center_lfe.0 {
	@args [ CARD ]
	@args.CARD {
		type string
	}
	type hooks
	slave.pcm {
		type hw
		card $CARD
		device 0
	}
	hooks.0 {
		type ctl_elems
		hook_args [
			{
				name "EMU10K1 PCM Send Volume"
				index { @func private_pcm_subdevice }
				lock true
				value [ 255 255 0 0 0 0 0 0  255 0 0 0 0 0 0 0  0 255 0 0 0 0 0 0 ]
			}
			{
				name "EMU10K1 PCM Send Routing"
				index { @func private_pcm_subdevice }
				lock true
				value [ 6 7 0 0 0 0 0 0  6 7 0 0 0 0 0 0  6 7 0 0 0 0 0 0 ]
			}
		]
	}
}

<confdir:pcm/side.conf>

Audigy2.pcm.side.0 {
	@args [ CARD ]
	@args.CARD {
		type string
	}
	type hooks
	slave.pcm {
		type hw
		card $CARD
		device 0
	}
	hooks.0 {
		type ctl_elems
		hook_args [
			{
				name "EMU10K1 PCM Send Volume"
				index { @func private_pcm_subdevice }
				lock true
				value [ 255 255 0 0 0 0 0 0  255 0 0 0 0 0 0 0  0 255 0 0 0 0 0 0 ]
			}
			{
				name "EMU10K1 PCM Send Routing"
				index { @func private_pcm_subdevice }
				lock true
				value [ 14 15 0 0 0 0 0 0  14 15 0 0 0 0 0 0  14 15 0 0 0 0 0 0 ]
			}
		]
	}
}

<confdir:pcm/surround40.conf>
<confdir:pcm/surround41.conf>
<confdir:pcm/surround50.conf>

Audigy2.pcm.surround40.0 {
	@args [ CARD ]
	@args.CARD {
		type string
	}
	type multi
	slaves [
		{
			pcm {
				@func concat
				strings [
					"cards.Audigy2.pcm.front.0:CARD=" $CARD
				]
			}
			channels 2
		}
		{
			pcm {
				@func concat
				strings [
					"cards.Audigy2.pcm.rear.0:CARD=" $CARD
				]
			}
			channels 2
		}
	]
	bindings [
		{ slave 0 channel 0 }
		{ slave 0 channel 1 }
		{ slave 1 channel 0 }
		{ slave 1 channel 1 }
	]
}

<confdir:pcm/surround51.conf>

Audigy2.pcm.surround51.0 {
	@args [ CARD ]
	@args.CARD {
		type string
	}
	type multi
	slaves [
		{
			pcm {
				@func concat
				strings [
					"cards.Audigy2.pcm.front.0:CARD=" $CARD
				]
			}
			channels 2
		}
		{
			pcm {
				@func concat
				strings [
					"cards.Audigy2.pcm.rear.0:CARD=" $CARD
				]
			}
			channels 2
		}
		{
			pcm {
				@func concat
				strings [
					"cards.Audigy2.pcm.center_lfe.0:CARD=" $CARD
				]
			}
			channels 2
		}
	]
	bindings [
		{ slave 0 channel 0 }
		{ slave 0 channel 1 }
		{ slave 1 channel 0 }
		{ slave 1 channel 1 }
		{ slave 2 channel 0 }
		{ slave 2 channel 1 }
	]
}

<confdir:pcm/surround71.conf>

Audigy2.pcm.surround71.0 {
	@args [ CARD ]
	@args.CARD {
		type string
	}
	type multi
	slaves [
		{
			pcm {
				@func concat
				strings [
					"cards.Audigy2.pcm.front.0:CARD=" $CARD
				]
			}
			channels 2
		}
		{
			pcm {
				@func concat
				strings [
					"cards.Audigy2.pcm.rear.0:CARD=" $CARD
				]
			}
			channels 2
		}
		{
			pcm {
				@func concat
				strings [
					"cards.Audigy2.pcm.center_lfe.0:CARD=" $CARD
				]
			}
			channels 2
		}
		{
			pcm {
				@func concat
				strings [
					"cards.Audigy2.pcm.side.0:CARD=" $CARD
				]
			}
			channels 2
		}
	]
	bindings [
		{ slave 0 channel 0 }
		{ slave 0 channel 1 }
		{ slave 1 channel 0 }
		{ slave 1 channel 1 }
		{ slave 2 channel 0 }
		{ slave 2 channel 1 }
		{ slave 3 channel 0 }
		{ slave 3 channel 1 }
	]
}

<confdir:pcm/iec958.conf>

Audigy2.pcm.iec958.0 {
	@args [ CARD AES0 AES1 AES2 AES3 ]
	@args.CARD {
		type string
	}
	@args.AES0 {
		type integer
	}
	@args.AES1 {
		type integer
	}
	@args.AES2 {
		type integer
	}
	@args.AES3 {
		type integer
	}
	type hooks
	slave.pcm {
		type hw
		card $CARD
		device 0
	}

	hooks.0 {
		type ctl_elems
		hook_args [
			{
				name "IEC958 Playback Default"
				preserve true
				value [ $AES0 $AES1 $AES2 $AES3 ]
			}
			{
				name "IEC958 Optical Raw Playback Switch"
				lock true
				preserve true
				value [ 1 1 ]
			}
			{
				name "EMU10K1 PCM Send Volume"
				index { @func private_pcm_subdevice }
				lock true
				value [ 255 255 0 0 0 0 0 0  255 0 0 0 0 0 0 0  0 255 0 0 0 0 0 0 ]
			}
			{
				name "EMU10K1 PCM Send Routing"
				index { @func private_pcm_subdevice }
				lock true
				value [ 20 21 0 0 0 0 0 0  20 21 0 0 0 0 0 0   20 21 0 0 0 0 0 0 ]
			}
			{
				name "Audigy Analog/Digital Output Jack"
				lock true
				preserve true
				value 1
			}
		]
	}
}

Index: alsa-driver/alsa-kernel/include/emu10k1.h
===================================================================
RCS file: /cvsroot/alsa/alsa-kernel/include/emu10k1.h,v retrieving revision 1.59

alsa-kernel/include/emu10k1.h +18 -0
diff -u -r1.59 emu10k1.h
@@ -1035,6 +1035,23 @@
 	void (*interrupt)(emu10k1_t *emu, unsigned int status);
 } emu10k1_midi_t;
 
+typedef struct {
+	u32 vendor;
+	u32 device;
+	u32 subsystem;
+	u32 emu10k1_chip; /* Original SB Live. Not SB Live 24bit. */
+	u32 emu10k2_chip; /* Audigy 1 or Audigy 2. */
+	u32 ca0102_chip;  /* Audigy 1 or Audigy 2. Not SB Audigy 2 Value. */
+	u32 ca0108_chip;  /* Audigy 2 Value */
+	u32 ca0151_chip;  /* P16V */
+	u32 spk71;        /* Has 7.1 speakers */
+	u32 spdif_bug;    /* Has Spdif phasing bug */
+	u32 ac97_chip;    /* Has an AC97 chip */
+	u32 ecard;        /* APS EEPROM */
+	char * driver;
+	char * name;
+} emu_chip_details_t;
+
 struct _snd_emu10k1 {
 	int irq;
 
@@ -1044,6 +1061,7 @@
 	    tos_link: 1,			/* tos link detected */
 	    rear_ac97: 1,			/* rear channels are on AC'97 */
 	    spk71:1;				/* 7.1 configuration (Audigy 2 ZS) */
+	emu_chip_details_t *card_capabilities;	/* Contains profile of card capabilities */
 	unsigned int audigy;			/* is Audigy? */
 	unsigned int revision;			/* chip revision */
 	unsigned int serial;			/* serial number */

Index: alsa-driver/alsa-kernel/pci/emu10k1/emu10k1.c
===================================================================
RCS file: /cvsroot/alsa/alsa-kernel/pci/emu10k1/emu10k1.c,v retrieving revision 1.33

alsa-kernel/pci/emu10k1/emu10k1.c +2 -16
diff -u -r1.33 emu10k1.c
@@ -198,22 +198,8 @@
 	}
 #endif
  
-	if (emu->audigy && (emu->serial == 0x10011102) ) {
-		strcpy(card->driver, "Audigy2");
-		strcpy(card->shortname, "Sound Blaster Audigy2_Value");
-	} else if (emu->audigy && (emu->revision == 4) ) {
-		strcpy(card->driver, "Audigy2");
-		strcpy(card->shortname, "Sound Blaster Audigy2");
-	} else if (emu->audigy) {
-		strcpy(card->driver, "Audigy");
-		strcpy(card->shortname, "Sound Blaster Audigy");
-	} else if (emu->APS) {
-		strcpy(card->driver, "E-mu APS");
-		strcpy(card->shortname, "E-mu APS");
-	} else {
-		strcpy(card->driver, "EMU10K1");
-		strcpy(card->shortname, "Sound Blaster Live!");
-	}
+	strcpy(card->driver, emu->card_capabilities->driver);
+	strcpy(card->shortname, emu->card_capabilities->name);
 	sprintf(card->longname, "%s (rev.%d, serial:0x%x) at 0x%lx, irq %i", card->shortname, emu->revision, emu->serial, emu->port, emu->irq);
 
 	if ((err = snd_card_register(card)) < 0) {

Index: alsa-driver/alsa-kernel/pci/emu10k1/emu10k1_main.c
===================================================================
RCS file: /cvsroot/alsa/alsa-kernel/pci/emu10k1/emu10k1_main.c,v retrieving revision 1.45

alsa-kernel/pci/emu10k1/emu10k1_main.c +55 -17

109 lines not on this page · raw message

Index: alsa-driver/alsa-kernel/pci/emu10k1/emufx.c
===================================================================
RCS file: /cvsroot/alsa/alsa-kernel/pci/emu10k1/emufx.c,v retrieving revision 1.69

alsa-kernel/pci/emu10k1/emufx.c +9 -1
diff -u -r1.69 emufx.c
@@ -1339,6 +1339,7 @@
 	/* A_PUT_STEREO_OUTPUT(A_EXTOUT_FRONT_L, A_EXTOUT_FRONT_R, playback + SND_EMU10K1_PLAYBACK_CHANNELS); */
 
 	/* IEC958 Optical Raw Playback Switch */ 
+	gpr_map[gpr++] = 0;
 	gpr_map[gpr++] = 0x1008;
 	gpr_map[gpr++] = 0xffff0000;
 	for (z = 0; z < 2; z++) {
@@ -1349,7 +1350,14 @@
 		A_SWITCH(icode, &ptr, tmp + 0, tmp + 2, gpr + z);
 		A_SWITCH_NEG(icode, &ptr, tmp + 1, gpr + z);
 		A_SWITCH(icode, &ptr, tmp + 1, playback + SND_EMU10K1_PLAYBACK_CHANNELS + z, tmp + 1);
-		A_OP(icode, &ptr, iACC3, A_EXTOUT(A_EXTOUT_FRONT_L + z), A_GPR(tmp + 0), A_GPR(tmp + 1), A_C_00000000);
+		if ((z==1) && (emu->card_capabilities->spdif_bug)) {
+			/* Due to a SPDIF output bug on some Audigy cards, this code delays the Right channel by 1 sample */
+			snd_printk("Installing spdif_bug patch: %s\n", emu->card_capabilities->name);
+			A_OP(icode, &ptr, iACC3, A_EXTOUT(A_EXTOUT_FRONT_L + z), A_GPR(gpr - 3), A_C_00000000, A_C_00000000);
+			A_OP(icode, &ptr, iACC3, A_GPR(gpr - 3), A_GPR(tmp + 0), A_GPR(tmp + 1), A_C_00000000);
+		} else {
+			A_OP(icode, &ptr, iACC3, A_EXTOUT(A_EXTOUT_FRONT_L + z), A_GPR(tmp + 0), A_GPR(tmp + 1), A_C_00000000);
+		}
 	}
 	snd_emu10k1_init_stereo_onoff_control(controls + nctl++, "IEC958 Optical Raw Playback Switch", gpr, 0);
 	gpr += 2;
#1 lore raw

At Sat, 19 Mar 2005 14:03:26 +0000,

Quoted 9 lines · James Courtier-Dutton

Hi,

This patch adds a DSP patch to fix an spdif_bug on some Audigy2 cards. It also does detailed identification of the capabilities of the sound card so that the patch is only applied to the sound cards that really need it.

Signed off: James Courtier-Dutton

The patch seems to have both this change and the addition of capabilities selection. Could you split into two patches?

thanks,

Takashi

-------------------------------------------------------
SF email is sponsored by - The IT Product Guide
Read honest & candid reviews on hundreds of IT Products from real users. Discover which products truly live up to the hype. Start reading now.
http://ads.osdn.com/?ad_id=6595&alloc_id=14396&op=click

#2 lore raw
Quoted 11 lines · Takashi Iwai

At Sat, 19 Mar 2005 14:03:26 +0000,

Quoted 6 lines · James Courtier-Dutton

Hi,

This patch adds a DSP patch to fix an spdif_bug on some Audigy2 cards.
It also does detailed identification of the capabilities of the sound card so that the patch is only applied to the sound cards that really need it.

Signed off: James Courtier-Dutton

Quoted 9 lines

The patch seems to have both this change and the addition of capabilities selection. Could you split into two patches?

thanks,

Takashi

Patch must be applied after the "[PATCH] Add framework for better audigy sound card capabilities selection." one.

Signoff: James Courtier-Dutton

This patch adds a DSP patch to fix an spdif_bug on some Audigy2 cards.

Index: alsa-driver/alsa-kernel/pci/emu10k1/emufx.c
===================================================================
RCS file: /cvsroot/alsa/alsa-kernel/pci/emu10k1/emufx.c,v retrieving revision 1.69

alsa-kernel/pci/emu10k1/emufx.c +9 -1
diff -u -r1.69 emufx.c
@@ -1339,6 +1339,7 @@
 	/* A_PUT_STEREO_OUTPUT(A_EXTOUT_FRONT_L, A_EXTOUT_FRONT_R, playback + SND_EMU10K1_PLAYBACK_CHANNELS); */
 
 	/* IEC958 Optical Raw Playback Switch */ 
+	gpr_map[gpr++] = 0;
 	gpr_map[gpr++] = 0x1008;
 	gpr_map[gpr++] = 0xffff0000;
 	for (z = 0; z < 2; z++) {
@@ -1349,7 +1350,14 @@
 		A_SWITCH(icode, &ptr, tmp + 0, tmp + 2, gpr + z);
 		A_SWITCH_NEG(icode, &ptr, tmp + 1, gpr + z);
 		A_SWITCH(icode, &ptr, tmp + 1, playback + SND_EMU10K1_PLAYBACK_CHANNELS + z, tmp + 1);
-		A_OP(icode, &ptr, iACC3, A_EXTOUT(A_EXTOUT_FRONT_L + z), A_GPR(tmp + 0), A_GPR(tmp + 1), A_C_00000000);
+		if ((z==1) && (emu->card_capabilities->spdif_bug)) {
+			/* Due to a SPDIF output bug on some Audigy cards, this code delays the Right channel by 1 sample */
+			snd_printk("Installing spdif_bug patch: %s\n", emu->card_capabilities->name);
+			A_OP(icode, &ptr, iACC3, A_EXTOUT(A_EXTOUT_FRONT_L + z), A_GPR(gpr - 3), A_C_00000000, A_C_00000000);
+			A_OP(icode, &ptr, iACC3, A_GPR(gpr - 3), A_GPR(tmp + 0), A_GPR(tmp + 1), A_C_00000000);
+		} else {
+			A_OP(icode, &ptr, iACC3, A_EXTOUT(A_EXTOUT_FRONT_L + z), A_GPR(tmp + 0), A_GPR(tmp + 1), A_C_00000000);
+		}
 	}
 	snd_emu10k1_init_stereo_onoff_control(controls + nctl++, "IEC958 Optical Raw Playback Switch", gpr, 0);
 	gpr += 2;
#3 lore raw

At Mon, 21 Mar 2005 21:26:29 +0000,

Quoted 25 lines · James Courtier-Dutton

[1 <text/plain; ISO-8859-1 (7bit)>]

Quoted 13 lines · Takashi Iwai

At Sat, 19 Mar 2005 14:03:26 +0000,

Quoted 8 lines · James Courtier-Dutton

Hi,

This patch adds a DSP patch to fix an spdif_bug on some Audigy2 cards. It also does detailed identification of the capabilities of the sound card so that the patch is only applied to the sound cards that really need it.

Signed off: James Courtier-Dutton

Quoted 9 lines

The patch seems to have both this change and the addition of capabilities selection. Could you split into two patches?

thanks,

Takashi

Quoted 7 lines

Patch must be applied after the "[PATCH] Add framework for better audigy sound card capabilities selection." one.

Signoff: James Courtier-Dutton

This patch adds a DSP patch to fix an spdif_bug on some Audigy2 cards.

OK, it's on CVS now. Thanks.

Takashi

-------------------------------------------------------
This SF.net email is sponsored by: 2005 Windows Mobile Application Contest Submit applications for Windows Mobile(tm)-based Pocket PCs or Smartphones for the chance to win $25,000 and application distribution. Enter today at
http://ads.osdn.com/?ad_id=6882&alloc_id=15148&op=click

#4 lore raw
Quoted 7 lines · Takashi Iwai
Quoted 7 lines
Quoted 1 line

Patch must be applied after the "[PATCH] Add framework for better audigy sound card capabilities selection." one.

Signoff: James Courtier-Dutton

Quoted 9 lines
Quoted 1 line

This patch adds a DSP patch to fix an spdif_bug on some Audigy2 cards.

OK, it's on CVS now. Thanks.

Takashi

Takashi,

Did you also apply the patch to the Audigy2.conf file, that goes with this patch? The spdif out routing has changed.

-------------------------------------------------------
This SF.net email is sponsored by Microsoft Mobile & Embedded DevCon 2005 Attend MEDC 2005 May 9-12 in Vegas. Learn more about the latest Windows Embedded(r) & Windows Mobile(tm) platforms, applications & content. Register by 3/29 & save $300 http://ads.osdn.com/?ad_id=6883&alloc_id=15149&op=click

#5 lore raw

At Wed, 23 Mar 2005 20:46:35 +0000,

Quoted 16 lines · James Courtier-Dutton
Quoted 14 lines · Takashi Iwai
Quoted 9 lines
Quoted 1 line

Patch must be applied after the "[PATCH] Add framework for better audigy sound card capabilities selection." one.

Signoff: James Courtier-Dutton

This patch adds a DSP patch to fix an spdif_bug on some Audigy2 cards.

OK, it's on CVS now. Thanks.

Quoted 8 lines
Quoted 3 lines

Takashi

Takashi,

Did you also apply the patch to the Audigy2.conf file, that goes with this patch? The spdif out routing has changed.

No, I didn't. Thus it's slipped from rc2 :-<
I applied it to CVS now.

Thanks for notice.

Takashi

-------------------------------------------------------
This SF.net email is sponsored by Microsoft Mobile & Embedded DevCon 2005 Attend MEDC 2005 May 9-12 in Vegas. Learn more about the latest Windows Embedded(r) & Windows Mobile(tm) platforms, applications & content. Register by 3/29 & save $300 http://ads.osdn.com/?ad_id=6883&alloc_id=15149&op=click

#7 lore raw
In reply to James Courtier-Dutton
Quoted 2 lines · James Courtier-Dutton

Did you also apply the patch to the Audigy2.conf file, that goes with this patch? The spdif out routing has changed.

when did this changed in the driver btw?

-------------------------------------------------------
This SF.net email is sponsored by Microsoft Mobile & Embedded DevCon 2005 Attend MEDC 2005 May 9-12 in Vegas. Learn more about the latest Windows Embedded(r) & Windows Mobile(tm) platforms, applications & content. Register by 3/29 & save $300 http://ads.osdn.com/?ad_id=6883&alloc_id=15149&op=click

#6 lore raw
In reply to Takashi Iwai

At Thu, 24 Mar 2005 12:24:40 +0100,

Quoted 23 lines · I wrote:

At Wed, 23 Mar 2005 20:46:35 +0000,

Quoted 20 lines · James Courtier-Dutton
Quoted 17 lines · Takashi Iwai
Quoted 9 lines
Quoted 1 line

Patch must be applied after the "[PATCH] Add framework for better audigy sound card capabilities selection." one.

Signoff: James Courtier-Dutton

This patch adds a DSP patch to fix an spdif_bug on some Audigy2 cards.

OK, it's on CVS now. Thanks.

Takashi

Quoted 6 lines
Quoted 4 lines

Takashi,

Did you also apply the patch to the Audigy2.conf file, that goes with this patch? The spdif out routing has changed.

No, I didn't. Thus it's slipped from rc2 :-<

Correction: It is included in rc2 (it was repackaged after my check-in).

Takashi

-------------------------------------------------------
This SF.net email is sponsored by Microsoft Mobile & Embedded DevCon 2005 Attend MEDC 2005 May 9-12 in Vegas. Learn more about the latest Windows Embedded(r) & Windows Mobile(tm) platforms, applications & content. Register by 3/29 & save $300 http://ads.osdn.com/?ad_id=6883&alloc_id=15149&op=click

#8 lore raw
In reply to Thierry Vignaud
Quoted 10 lines · Thierry Vignaud
Quoted 2 lines · James Courtier-Dutton

Did you also apply the patch to the Audigy2.conf file, that goes with this patch? The spdif out routing has changed.

when did this changed in the driver btw?

1.0.9rc2 needs the changed Audigy2.conf file, otherwise AC3 passthru will not work. The DSP code in alsa-driver changed to fix a hardware bug in some Creative sound cards,

-------------------------------------------------------
SF email is sponsored by - The IT Product Guide
Read honest & candid reviews on hundreds of IT Products from real users. Discover which products truly live up to the hype. Start reading now.
http://ads.osdn.com/?ad_id=6595&alloc_id=14396&op=click

#9 lore raw
Quoted 8 lines · James Courtier-Dutton
Quoted 4 lines
Quoted 2 lines

Did you also apply the patch to the Audigy2.conf file, that goes with this patch? The spdif out routing has changed.

when did this changed in the driver btw?

1.0.9rc2 needs the changed Audigy2.conf file, otherwise AC3 passthru will not work. The DSP code in alsa-driver changed to fix a hardware bug in some Creative sound cards,

i guess that this's this commit:

2005-03-22 21:58 tiwai

* pci/emu10k1/emufx.c: Summary: Fixes AC3 output on Audigy2 sound cards

This patch adds a DSP patch to fix an spdif_bug on some Audigy2 cards.

is'nt it?

-------------------------------------------------------
SF email is sponsored by - The IT Product Guide
Read honest & candid reviews on hundreds of IT Products from real users. Discover which products truly live up to the hype. Start reading now.
http://ads.osdn.com/?ad_id=6595&alloc_id=14396&op=click