Index: ivtv/driver/ivtv-api.c
diff -u ivtv/driver/ivtv-api.c:1.1 ivtv/driver/ivtv-api.c:1.4
--- ivtv/driver/ivtv-api.c:1.1	Sun Mar  7 11:26:54 2004
+++ ivtv/driver/ivtv-api.c	Sun Mar 21 08:24:56 2004
@@ -31,6 +31,7 @@
 static int ivtv_v4l2_ioctl(struct inode *inode, struct file *filp,
 			   unsigned int cmd, unsigned long arg);
 static int ivtv_v4l2_pre_init(struct ivtv *itv);
+static void set_mode_change_callback(struct ivtv *itv, int enabled);
 
 struct file_operations ivtv_v4l2_fops = {
 	read:           ivtv_v4l2_read,
@@ -466,6 +467,88 @@
 
 u32 ivtv_audio_mask_generation = 0x10000;
 
+struct v4l2_queryctrl ivtv_ctrl_menu_dyn = {
+        .id            = V4L2_CID_IVTV_DYN,
+        .type          = V4L2_CTRL_TYPE_MENU,
+        .name          = "Dynamic Stereo/Mono Watch",
+        .minimum       = 0,
+        .maximum       = 2,
+        .step          = 1,
+        .default_value = 0,
+        .flags         = 0,
+        .reserved      = {0,0}
+};
+
+struct v4l2_querymenu ivtv_ctrl_query_dyn[] = {
+        /* ID, Index, Name, Reserved */
+        {V4L2_CID_IVTV_DYN, 0, "Disabled",            0},
+        {V4L2_CID_IVTV_DYN, 1, "Always mono",         0},
+        {V4L2_CID_IVTV_DYN, 2, "Stereo if available", 0}
+};
+
+u32 ivtv_audio_tbl_dyn[] = {
+        /* setting */
+        IVTV_CODEC_AUDIO_DYNSTEREO_STATIC,
+        IVTV_CODEC_AUDIO_DYNSTEREO_MONO,
+        IVTV_CODEC_AUDIO_DYNSTEREO_STEREO
+};
+
+u32 ivtv_audio_mask_dyn = IVTV_CODEC_AUDIO_DYNSTEREO_ENABLED_MASK ;
+
+struct v4l2_queryctrl ivtv_ctrl_menu_dyn_joint = {
+        .id            = V4L2_CID_IVTV_DYN_JOINT,
+        .type          = V4L2_CTRL_TYPE_MENU,
+        .name          = "Dynamic Stereo Mode",
+        .minimum       = 0,
+        .maximum       = 1,
+        .step          = 1,
+        .default_value = 0,
+        .flags         = 0,
+        .reserved      = {0,0}
+};
+
+struct v4l2_querymenu ivtv_ctrl_query_dyn_joint [] = {
+        /* ID, Index, Name, Reserved */
+        {V4L2_CID_IVTV_DYN_JOINT, 0, "Split",  0},
+        {V4L2_CID_IVTV_DYN_JOINT, 1, "Joint",  0}
+};
+
+u32 ivtv_audio_tbl_dyn_joint[] = {
+        /* setting */
+        IVTV_CODEC_AUDIO_DYNSTEREO_SPLIT,
+        IVTV_CODEC_AUDIO_DYNSTEREO_JOINT
+};
+
+u32 ivtv_audio_mask_dyn_joint = IVTV_CODEC_AUDIO_DYNSTEREO_JOINT_MASK;
+
+struct v4l2_queryctrl ivtv_ctrl_menu_dyn_lang = {
+        .id            = V4L2_CID_IVTV_DYN_LANG,
+        .type          = V4L2_CTRL_TYPE_MENU,
+        .name          = "Dynamic Language Selection",
+        .minimum       = 0,
+        .maximum       = 2,
+        .step          = 1,
+        .default_value = 0,
+        .flags         = 0,
+        .reserved      = {0,0}
+};
+
+struct v4l2_querymenu ivtv_ctrl_query_dyn_lang [] = {
+        /* ID, Index, Name, Reserved */
+        {V4L2_CID_IVTV_DYN_LANG, 0, "Language 1",  0},
+        {V4L2_CID_IVTV_DYN_LANG, 2, "Language 2",  0},
+        {V4L2_CID_IVTV_DYN_LANG, 3, "Bilingual (Dual)",  0}
+};
+
+u32 ivtv_audio_tbl_dyn_lang[] = {
+        /* setting */
+        IVTV_CODEC_AUDIO_DYNSTEREO_LANG1,
+        IVTV_CODEC_AUDIO_DYNSTEREO_LANG2,
+        IVTV_CODEC_AUDIO_DYNSTEREO_DUAL
+};
+
+u32 ivtv_audio_mask_dyn_lang = IVTV_CODEC_AUDIO_DYNSTEREO_LANG_MASK;
+
 
 /* 3 stream types: mpeg, yuv, passthru */
 struct ivtv_v4l2_stream tmk_mpg_stream = {
@@ -802,6 +885,212 @@
 	return retval;
 }
 
+static int mode_change_callback(int oldstereo, int newstereo, void* userdata) {
+        /*
+         * called by msp3400.c when a stereo mode change has been detected, 
+         * on msp3400's watchstereo thread, mostly
+         */
+        struct ivtv* ivtv = (struct ivtv*)userdata;
+        int retval = oldstereo; // default 'static' behaviour (no change)
+        u32 audio_bitmap;
+        IVTV_DEBUG(IVTV_DEBUG_INFO, 
+                   "stereo mode_change_callback(%d, %d, 0x%lx) start.\n", 
+                   oldstereo, 
+                   newstereo, 
+                   (unsigned long)userdata);
+        
+        if(!ivtv) { // that's obviously wrong. 
+                IVTV_DEBUG(IVTV_DEBUG_ERR,
+                           "NULL userdata passed to mode_change_callback(%d, %d, NULL).\n", 
+                           oldstereo, 
+                           newstereo);
+                return retval; 
+        }
+
+        audio_bitmap = ivtv->v4l2.codec.audio_bitmap;
+        if( (audio_bitmap & IVTV_CODEC_AUDIO_DYNSTEREO_ENABLED_MASK) != 0 ) {
+                u32 data = ivtv->v4l2.codec.audio_bitmap;
+                const u32 old_mpeg_stereo = data&IVTV_CODEC_AUDIO_MPEG_STEREO_MASK;
+                u32 new_mpeg_stereo = old_mpeg_stereo;
+                
+                if( (newstereo&VIDEO_SOUND_LANG1) ) { /* bilingual audio mode detected */
+                        switch(audio_bitmap & IVTV_CODEC_AUDIO_DYNSTEREO_LANG_MASK) {
+                                
+                        case IVTV_CODEC_AUDIO_DYNSTEREO_LANG1:
+                                new_mpeg_stereo=IVTV_CODEC_AUDIO_MPEG_STEREO_MONO;
+                                retval=VIDEO_SOUND_LANG1;
+                                break;
+                        case IVTV_CODEC_AUDIO_DYNSTEREO_LANG2:
+                                new_mpeg_stereo=IVTV_CODEC_AUDIO_MPEG_STEREO_MONO;
+                                retval=VIDEO_SOUND_LANG2;
+                                break;
+                        case IVTV_CODEC_AUDIO_DYNSTEREO_DUAL:
+                                new_mpeg_stereo=IVTV_CODEC_AUDIO_MPEG_STEREO_DUAL;
+                                retval=VIDEO_SOUND_STEREO;
+                                break;
+                        default:
+                                IVTV_DEBUG(IVTV_DEBUG_ERR,
+                                           "unknown audio_bitmap dynstereo lang value 0x%lx\n",
+                                           audio_bitmap & IVTV_CODEC_AUDIO_DYNSTEREO_LANG_MASK);
+                                break;
+                        }
+                } else if( (newstereo&VIDEO_SOUND_STEREO) ) { /* stereo audio mode detected */
+                        switch(audio_bitmap & IVTV_CODEC_AUDIO_DYNSTEREO_ENABLED_MASK) {
+                                
+                        case IVTV_CODEC_AUDIO_DYNSTEREO_STEREO:
+                                if( (audio_bitmap & IVTV_CODEC_AUDIO_DYNSTEREO_JOINT) ) {
+                                        new_mpeg_stereo=IVTV_CODEC_AUDIO_MPEG_STEREO_JOINT;
+                                } else {
+                                        new_mpeg_stereo=IVTV_CODEC_AUDIO_MPEG_STEREO_STEREO;
+                                }
+                                retval=VIDEO_SOUND_STEREO;
+                                break;
+                        case IVTV_CODEC_AUDIO_DYNSTEREO_MONO:
+                                new_mpeg_stereo=IVTV_CODEC_AUDIO_MPEG_STEREO_MONO;
+                                retval=VIDEO_SOUND_MONO;
+                                break;
+                        default:
+                                IVTV_DEBUG(IVTV_DEBUG_ERR,
+                                           "unknown audio_bitmap dynstereo enabled value 0x%lx\n",
+                                           audio_bitmap & IVTV_CODEC_AUDIO_DYNSTEREO_ENABLED_MASK);
+                                break;
+                        }
+                } else { /* mono audio mode detected */
+                        new_mpeg_stereo=IVTV_CODEC_AUDIO_MPEG_STEREO_MONO;
+                        retval=VIDEO_SOUND_MONO;
+                }
+                
+                if( new_mpeg_stereo != old_mpeg_stereo ) {
+                        u32 newdata[IVTV_MBOX_MAX_DATA]; 
+                        u32 result; 
+                        const u32 new_audio_bitmap = ( data & ( ~ IVTV_CODEC_AUDIO_MPEG_STEREO_MASK ) ) | new_mpeg_stereo;
+                        IVTV_DEBUG(IVTV_DEBUG_INFO,
+                                   "set audio mode from 0x%lx to 0x%lx\n", 
+                                   data, 
+                                   newdata[0]);
+                        
+                        newdata[0]=new_audio_bitmap & 0xffff;
+                        int x = ivtv_api(ivtv->enc_mbox, 
+                                         &ivtv->enc_msem, 
+                                         IVTV_API_ASSIGN_AUDIO_PROPERTIES,
+                                         &result, 
+                                         1, 
+                                         &newdata[0]);
+                        if (x) {
+                                IVTV_DEBUG(IVTV_DEBUG_ERR, "IVTV_API_ASSIGN_AUDIO_PROPERTIES error. Code %d\n",x);
+                        } else { 
+                                ivtv->v4l2.codec.audio_bitmap=new_audio_bitmap;
+                        }
+                }
+        }
+        IVTV_DEBUG(IVTV_DEBUG_INFO,
+                   "mode_change_callback(%d, %d, 0x%lx) -> %d\n", 
+                   oldstereo, 
+                   newstereo, 
+                   (unsigned long)userdata,
+                   retval);
+        return retval;
+}
+/* called when stereo mode configuration was changed and
+ * the callback needs to be called again to update
+ * stereo settings.
+ */
+static int update_stereo_mode(struct ivtv *ivtv) {
+        /* this function is complicated just because
+         * values must be converted from v4l1 into v4l2
+         * and back... the worst is that msp3400.c will
+         * do the same conversions, but backwards. yet,
+         * I have to use the v4l2 calls for that because
+         * VIDIOCGAUDIO will not give me all the information
+         * I need.
+         */
+        struct v4l2_tuner vt;
+        memset(&vt, 0, sizeof(struct v4l2_tuner));
+        ivtv_call_i2c_client(ivtv,IVTV_MSP3400_I2C_ADDR,VIDIOC_G_TUNER, &vt);        
+        u32 subchans = vt.rxsubchans;
+        int mode=0;
+        int oldmode=0;
+        int newmode;
+        // v4l2 subchans -> v4l1 detected mode
+        if(subchans&V4L2_TUNER_SUB_MONO)
+                mode|=VIDEO_SOUND_MONO;
+        if(subchans&V4L2_TUNER_SUB_STEREO)
+                mode|=VIDEO_SOUND_STEREO;
+        if(subchans&V4L2_TUNER_SUB_LANG1)
+                mode|=VIDEO_SOUND_LANG1;
+        if(subchans&V4L2_TUNER_SUB_LANG2)
+                mode|=VIDEO_SOUND_LANG2;
+        // v4l2 audiomode -> v4l1 current mode
+        switch(vt.audmode) {
+        case V4L2_TUNER_MODE_LANG1:
+                oldmode=VIDEO_SOUND_LANG1;
+                break;
+        case V4L2_TUNER_MODE_LANG2:
+                oldmode=VIDEO_SOUND_LANG2;
+                break;
+        case V4L2_TUNER_MODE_STEREO:
+                oldmode=VIDEO_SOUND_STEREO;
+                break;
+        case V4L2_TUNER_MODE_MONO:
+        default:
+                oldmode=VIDEO_SOUND_MONO;
+                break;
+        }
+        newmode=mode_change_callback(oldmode, mode, ivtv);
+        printk("update_stereo_mode: mode_change_callback(%d, %d, 0x%lx) -> %d\n", 
+               oldmode,
+               mode,
+               ivtv,
+               newmode);
+        if(newmode!=oldmode) {
+                // v4l1 newmode -> v4l2 audiomode
+                u32 audmode;
+                if(newmode&VIDEO_SOUND_LANG1) {
+                        audmode=V4L2_TUNER_MODE_LANG1;
+                } else if(newmode&VIDEO_SOUND_LANG2) {
+                        audmode=V4L2_TUNER_MODE_LANG2;
+                } else if(newmode&VIDEO_SOUND_STEREO) {
+                        audmode=V4L2_TUNER_MODE_STEREO;
+                } else {
+                        audmode=V4L2_TUNER_MODE_MONO;
+                }
+                if(audmode!=vt.audmode) {
+                        vt.audmode=audmode;
+                        ivtv_call_i2c_client(ivtv,IVTV_MSP3400_I2C_ADDR,VIDIOC_S_TUNER, &vt);        
+                        return 1;
+                }
+        }
+        return 0;
+}
+
+/* add the value in ivtv_dynstereo to audio_bitmap if
+ * the higher bits are null (it's just a temporary hack, 
+ * I hope) 
+ */
+static void set_default_ivtv_dynstereo(struct ivtv* ivtv) {
+	if( ! (ivtv->v4l2.codec.audio_bitmap&IVTV_CODEC_AUDIO_DYNSTEREO_MASK) ) {
+	   ivtv->v4l2.codec.audio_bitmap|=(ivtv_dynstereo&0xffff0000);
+	}
+}
+
+
+static void set_mode_change_callback(struct ivtv* ivtv, int enabled) {
+        if(enabled) {
+                struct msp_stereo_event ev;
+                ev.callback = mode_change_callback;
+                ev.userdata = ivtv;
+                ivtv_call_i2c_client(ivtv,
+                                     IVTV_MSP3400_I2C_ADDR,
+                                     MSP_STEREO_EVENT,
+                                     &ev);
+        } else {
+                ivtv_call_i2c_client(ivtv,
+                                     IVTV_MSP3400_I2C_ADDR,
+                                     MSP_STEREO_EVENT,
+                                     NULL);
+        }
+}
+
 /* After setting the audio.active param, call this to
  *  get the right input.. think of it as a resolver */
 int ivtv_set_audio(struct ivtv *itv, int *map) {
@@ -1002,12 +1291,34 @@
 	itv->v4l2.audio_meta[8].setting = ivtv_ctrl_menu_generation.default_value;
 	itv->v4l2.audio_meta[8].table = &ivtv_audio_tbl_generation[0];
 
+	/* V4L2_CID_IVTV_DYN*/
+	itv->v4l2.audio_meta[9].ctrl = &ivtv_ctrl_menu_dyn;
+	itv->v4l2.audio_meta[9].menu = ivtv_ctrl_query_dyn;
+	itv->v4l2.audio_meta[9].mask = ivtv_audio_mask_dyn;
+	itv->v4l2.audio_meta[9].setting = ivtv_ctrl_menu_dyn.default_value;
+	itv->v4l2.audio_meta[9].table = &ivtv_audio_tbl_dyn[0];
+
+	/* V4L2_CID_IVTV_DYN_JOINT*/
+	itv->v4l2.audio_meta[10].ctrl = &ivtv_ctrl_menu_dyn_joint;
+	itv->v4l2.audio_meta[10].menu = ivtv_ctrl_query_dyn_joint;
+	itv->v4l2.audio_meta[10].mask = ivtv_audio_mask_dyn_joint;
+	itv->v4l2.audio_meta[10].setting = ivtv_ctrl_menu_dyn_joint.default_value;
+	itv->v4l2.audio_meta[10].table = &ivtv_audio_tbl_dyn_joint[0];
+
+	/* V4L2_CID_IVTV_DYN_LANG*/
+	itv->v4l2.audio_meta[11].ctrl = &ivtv_ctrl_menu_dyn_lang;
+	itv->v4l2.audio_meta[11].menu = ivtv_ctrl_query_dyn_lang;
+	itv->v4l2.audio_meta[11].mask = ivtv_audio_mask_dyn_lang;
+	itv->v4l2.audio_meta[11].setting = ivtv_ctrl_menu_dyn_lang.default_value;
+	itv->v4l2.audio_meta[11].table = &ivtv_audio_tbl_dyn_lang[0];
+
 	itv->v4l2.codec.audio_bitmap = 0; 
 	for (x = 0; x < IVTV_V4L2_AUDIO_MENUCOUNT; x++) {
 		temp = itv->v4l2.audio_meta[x].setting;
 		itv->v4l2.codec.audio_bitmap |= 
 			itv->v4l2.audio_meta[x].table[temp];
 	}
+	set_default_ivtv_dynstereo(itv);
 
 	retval = ivtv_set_audio(itv,tmk_audio_mapping);
 	if (retval) {
@@ -1015,6 +1326,8 @@
 		return retval;
 	}
 
+        set_mode_change_callback(itv, 1/*enable*/);
+
 	//FIXME Setup components here? tuner channel etc
 	return 0;
 }
@@ -1140,11 +1453,17 @@
 	if (x) IVTV_DEBUG(IVTV_DEBUG_ERR, "init error 11. Code %d\n",x);
 		
 	/*assign audio properties */
-	data[0] = itv->v4l2.codec.audio_bitmap;
+        data[0] = itv->v4l2.codec.audio_bitmap & 0xffff;
         x = ivtv_api(itv->enc_mbox, &itv->enc_msem, IVTV_API_ASSIGN_AUDIO_PROPERTIES,
-		&result, 1, &data[0]);
-	if (x) IVTV_DEBUG(IVTV_DEBUG_ERR, "init error 12. Code %d\n",x);
-
+                     &result, 1, &data[0]);
+        if (x) IVTV_DEBUG(IVTV_DEBUG_ERR, "init error 12. Code %d\n",x);
+        if( (itv->v4l2.codec.audio_bitmap & IVTV_CODEC_AUDIO_DYNSTEREO_ENABLED_MASK) ) {
+                /* update the stereo part of this property using the msp3400,
+                 * through the callback, if necessary
+                 */
+                update_stereo_mode(itv);
+        }
+        
 	/*assign dnr filter mode */
 	data[0] = itv->v4l2.codec.dnr_mode;
 	data[1] = itv->v4l2.codec.dnr_type;
@@ -1412,7 +1731,7 @@
 		} else {
 			data[2] = 480; /* YUV source height*/
 		}
-		data[3] = itv->v4l2.codec.audio_bitmap; /* Audio settings to use,
+		data[3] = itv->v4l2.codec.audio_bitmap & 0xffff; /* Audio settings to use,
 							   bitmap. see docs.*/
 		x = ivtv_api(itv->dec_mbox,
 			     &itv->dec_msem,
@@ -1480,6 +1799,7 @@
 	int x;
 
 	IVTV_DEBUG(IVTV_DEBUG_INFO,"v4l2 unregister\n");
+        set_mode_change_callback(itv, 0/*disable*/);
 
 	if (atomic_read(&itv->capturing) >= 0) ivtv_stop_all_captures(itv);
 	if (itv->v4l2.tuner.table.tuner) kfree(itv->v4l2.tuner.table.tuner);
@@ -2232,6 +2552,8 @@
 			int off = vctrl->id - V4L2_CID_PRIVATE_BASE;
 			s32 v = vctrl->value;
 			if (off < IVTV_V4L2_AUDIO_MENUCOUNT) {
+                                u32 audio_bitmap_dyn = itv->v4l2.codec.audio_bitmap&IVTV_CODEC_AUDIO_DYNSTEREO_MASK;
+
 				if ((v<=itv->v4l2.audio_meta[off].ctrl->maximum) &&
 				    (v>=itv->v4l2.audio_meta[off].ctrl->minimum)) {
 					itv->v4l2.audio_meta[off].setting = v;
@@ -2254,6 +2576,10 @@
 						"ctrl: value out of range\n");
 				return -ERANGE;
 				}
+                                set_default_ivtv_dynstereo(itv);
+                                if( audio_bitmap_dyn != itv->v4l2.codec.audio_bitmap&IVTV_CODEC_AUDIO_DYNSTEREO_MASK) {
+                                        update_stereo_mode(itv);
+                                }
 			} else {
 				switch (vctrl->id) {
 				case V4L2_CID_IVTV_DEC_SMOOTH_FF:
@@ -2605,6 +2931,7 @@
 		//FIXME copy_from_user needed
 		struct v4l2_frequency *vf=(struct v4l2_frequency *)arg;
 		struct msp_matrix mspm;
+		unsigned long freq=0;
 
 		IVTV_DEBUG(IVTV_DEBUG_INFO,"v4l2 ioctl: set frequency\n");
 
@@ -2632,7 +2959,7 @@
 
 		/* Unmute */
 		ivtv_set_audio(itv,tmk_audio_mapping);
-
+		ivtv_call_i2c_client(itv,IVTV_MSP3400_I2C_ADDR, VIDIOCSFREQ,&freq);
 		break;
 	}
 	case VIDIOC_ENUMSTD: {
@@ -2715,15 +3042,15 @@
 	case VIDIOC_S_TUNER: { /* Setting tuner can only set audio mode */
 		//FIXME copy_from_user needed
 		struct v4l2_tuner *vt=(struct v4l2_tuner *)arg;
+		struct video_audio va;
 
 		IVTV_DEBUG(IVTV_DEBUG_INFO,"v4l2 ioctl: set tuner\n");
 
 		if ((vt->index < 0) || (vt->index >= itv->v4l2.tuner.count))
 			return -EINVAL;
-		/* looks like tuner.c doesn't support selection 
-		 * fallback to stereo... */
-		vt->audmode = V4L2_TUNER_MODE_STEREO;
 
+		/* Regarding audio, the demodulator is our tuner */
+                ivtv_call_i2c_client(itv,IVTV_MSP3400_I2C_ADDR,VIDIOC_S_TUNER, vt);
 		break;
 	}
 	case VIDIOC_G_TUNER: {
@@ -2748,6 +3075,9 @@
 		} else {
 			vt->signal = 0;
 		}
+
+		/* ask msp3400 to set audmode and rxsubchans */
+                ivtv_call_i2c_client(itv,IVTV_MSP3400_I2C_ADDR,VIDIOC_G_TUNER, vt);
 		break;
 	}
 	case MSP_SET_MATRIX: {
@@ -2795,8 +3125,13 @@
 			return -EINVAL;
 		} else {
 	  		/* Passed the garbage check */
+                        u32 audio_bitmap_dyn = itv->v4l2.codec.audio_bitmap&IVTV_CODEC_AUDIO_DYNSTEREO_MASK;
 	  		memcpy(&(itv->v4l2.codec), codec,
 				sizeof(struct ivtv_ioctl_codec));
+                        set_default_ivtv_dynstereo(itv);
+                        if( audio_bitmap_dyn != (codec->audio_bitmap&IVTV_CODEC_AUDIO_DYNSTEREO_MASK)) {
+                                update_stereo_mode(itv);
+                        }
 		}
 
 		/* VCD streamtype has some quirks. Handle them here */
Index: ivtv/driver/ivtv-driver.c
diff -u ivtv/driver/ivtv-driver.c:1.2 ivtv/driver/ivtv-driver.c:1.3
--- ivtv/driver/ivtv-driver.c:1.2	Sun Mar  7 11:41:14 2004
+++ ivtv/driver/ivtv-driver.c	Sun Mar 21 08:24:56 2004
@@ -72,6 +72,7 @@
 #endif
 
 int ivtv_pal = 0;
+long ivtv_dynstereo = 0x00000000;
 
 
 /* low debugging by default */
@@ -164,6 +165,12 @@
 MODULE_PARM(ivtv_pal, "i");
 MODULE_PARM_DESC(ivtv_pal, "\nUse PAL as default video mode instead of NTSC");
 
+MODULE_PARM(ivtv_dynstereo, "l");
+MODULE_PARM_DESC(ivtv_dynstereo, 
+				 "\nForce dynamic stereo configuration. This sets the default for\n"
+				 "the first 2 bytes of the audio_bitmap field. See, in ivtv.h,\n"
+				 "the IVTV_CODEC_AUDIO_DYNSTEREO_ flags.");
+
 MODULE_AUTHOR("Kevin Thayer");
 MODULE_DESCRIPTION("Alpha iTVC15 driver");
 MODULE_SUPPORTED_DEVICE("iTVC15/16 mpg2 encoder (aka WinTV PVR 250/350)");
Index: ivtv/driver/ivtv.h
diff -u ivtv/driver/ivtv.h:1.2 ivtv/driver/ivtv.h:1.4
--- ivtv/driver/ivtv.h:1.2	Sun Mar  7 11:41:14 2004
+++ ivtv/driver/ivtv.h	Sun Mar 21 08:24:56 2004
@@ -439,8 +439,11 @@
 #define V4L2_CID_IVTV_CRC	(V4L2_CID_PRIVATE_BASE + 6)
 #define V4L2_CID_IVTV_COPYRIGHT	(V4L2_CID_PRIVATE_BASE + 7)
 #define V4L2_CID_IVTV_GEN	(V4L2_CID_PRIVATE_BASE + 8)
+#define V4L2_CID_IVTV_DYN	(V4L2_CID_PRIVATE_BASE + 9)
+#define V4L2_CID_IVTV_DYN_JOINT	(V4L2_CID_PRIVATE_BASE + 10)
+#define V4L2_CID_IVTV_DYN_LANG	(V4L2_CID_PRIVATE_BASE + 11)
 
-#define IVTV_V4L2_AUDIO_MENUCOUNT 9 /* # of v4l controls */
+#define IVTV_V4L2_AUDIO_MENUCOUNT 12 /* # of v4l controls */
 
 #define IVTV_DEC_PRIVATE_BASE	(V4L2_CID_PRIVATE_BASE + IVTV_V4L2_AUDIO_MENUCOUNT)
 
@@ -527,7 +530,7 @@
 /* For use with IVTV_IOC_G_CODEC and IVTV_IOC_S_CODEC */
 struct ivtv_ioctl_codec {
 	u32 aspect;
-	u32 audio_bitmap;
+    u32 audio_bitmap; /* see IVTV_CODEC_AUDIO  */
 	u32 bframes;
 	u32 bitrate_mode;
 	u32 bitrate;
@@ -543,8 +546,35 @@
 	u32 stream_type;
 };
 
+/* audio_bitmap: 
+ * bits 0-15 follow iso11172, mostly, see http://ivtv.sourceforge.net/firmware-api.html. 
+ * bits 16-32 are ivtv driver extensions as defined below:
+ */
+#define IVTV_CODEC_AUDIO_MPEG_STEREO_MASK        (0x03 << 8)
+#define IVTV_CODEC_AUDIO_MPEG_STEREO_STEREO      (0x00 << 8)
+#define IVTV_CODEC_AUDIO_MPEG_STEREO_JOINT       (0x01 << 8)
+#define IVTV_CODEC_AUDIO_MPEG_STEREO_DUAL        (0x02 << 8)
+#define IVTV_CODEC_AUDIO_MPEG_STEREO_MONO        (0x03 << 8)
+
+#define IVTV_CODEC_AUDIO_DYNSTEREO_STATIC        (0x00 << 16) /* compatibility mode, strictly follow audio mode in bits 9:8 */
+#define IVTV_CODEC_AUDIO_DYNSTEREO_MONO          (0x01 << 16) /* always force mono mode 0x00010000 */
+#define IVTV_CODEC_AUDIO_DYNSTEREO_STEREO        (0x02 << 16) /* use stereo mode if available 0x00020000 */
+#define IVTV_CODEC_AUDIO_DYNSTEREO_ENABLED_MASK  (0x03 << 16) /* mask for this setting (static/mono/stereo) 0x00030000*/
+
+#define IVTV_CODEC_AUDIO_DYNSTEREO_SPLIT         (0x0 << 17)  /* when encoding in stereo, use split mode */
+#define IVTV_CODEC_AUDIO_DYNSTEREO_JOINT         (0x1 << 17)  /* when encoding in stereo, use JOINT mode 0x00040000*/
+#define IVTV_CODEC_AUDIO_DYNSTEREO_JOINT_MASK    (0x1 << 17)  /* mask for this setting (split/joint) */
+
+#define IVTV_CODEC_AUDIO_DYNSTEREO_LANG1         (0x00 << 19) /* record only lang1 of bilingual programs */
+#define IVTV_CODEC_AUDIO_DYNSTEREO_LANG2         (0x01 << 19) /* record only lang2 of bilingual programs 0x00080000*/
+#define IVTV_CODEC_AUDIO_DYNSTEREO_DUAL          (0x02 << 19) /* record both lang1 and lang2 of bilingual programs 0x00100000 */
+#define IVTV_CODEC_AUDIO_DYNSTEREO_LANG_MASK     (0x03 << 19) /* mask for this setting (lang1/lang2/dual) 0x00180000 */
+
+#define IVTV_CODEC_AUDIO_DYNSTEREO_MASK          (0x1f << 16)
+
 extern int ivtv_debug;
 extern int ivtv_pal;
+extern long ivtv_dynstereo;
 
 /* Scatter-Gather array element, used in DMA transfers */
 struct ivtv_SG_element {
Index: ivtv/driver/msp3400.c
diff -u ivtv/driver/msp3400.c:1.1 ivtv/driver/msp3400.c:1.5
--- ivtv/driver/msp3400.c:1.1	Sun Mar  7 11:26:54 2004
+++ ivtv/driver/msp3400.c	Sun Mar 21 10:09:28 2004
@@ -44,6 +44,7 @@
 #include 
 #include 
 #include 
+#include 
 #include 
 #include 
 
@@ -84,7 +85,9 @@
 	int nicam;
 	int mode;
 	int norm;
-	int stereo;
+        int stereo; /* current stereo mode, set by msp3400c_setstereo() */
+        int detected_stereo; /* detected stereo mode, set by autodetect_stereo */
+	struct msp_stereo_event stereo_event;
 	int nicam_on;
 	int acb;
 	int main, second;	/* sound carrier */
@@ -101,7 +104,7 @@
 	struct task_struct  *thread;
 	wait_queue_head_t    wq;
 
-	struct semaphore    *notify;
+	struct semaphore    *notify, *ack;
 	int                  active,restart,rmmod;
 
 	int                  watch_stereo;
@@ -411,6 +414,7 @@
 	dprintk("msp3400: setmode: %d\n",type);
 	msp->mode   = type;
 	msp->stereo = VIDEO_SOUND_MONO;
+        msp->detected_stereo = VIDEO_SOUND_MONO;
 
 	msp3400c_write(client,I2C_MSP3400C_DEM, 0x00bb,          /* ad_cv */
 		       msp_init_data[type].ad_cv);
@@ -482,6 +486,11 @@
 	int nicam=0; /* channel source: FM/AM or nicam */
 	int src=0;
 
+        if( (mode&(VIDEO_SOUND_LANG1|VIDEO_SOUND_LANG2)) == (VIDEO_SOUND_LANG1|VIDEO_SOUND_LANG2) ) {
+                mode=VIDEO_SOUND_LANG1;
+        }
+        msp->stereo=mode;
+        
 	/* switch demodulator */
 	switch (msp->mode) {
 	case MSP_MODE_FM_TERRA:
@@ -636,7 +645,7 @@
 {
 	struct msp3400c *msp = client->data;
 	int val;
-	int newstereo = msp->stereo;
+	int newstereo = msp->detected_stereo;
 	int newnicam  = msp->nicam_on;
 	int update = 0;
 
@@ -704,12 +713,16 @@
 		if (val & 0x0100) newstereo |= VIDEO_SOUND_LANG1;
 		break;
 	}
-	if (newstereo != msp->stereo) {
-		update = 1;
-		dprintk("msp34xx: watch: stereo %d => %d\n",
-			msp->stereo,newstereo);
-		msp->stereo   = newstereo;
-	}
+        dprintk("msp34xx: watch: detected_stereo=%d, newstereo=%d\n",
+                msp->detected_stereo,
+                newstereo);
+        if(msp->detected_stereo!=newstereo) {
+                dprintk("msp34xx: watch: stereo %d => %d\n",
+                        msp->detected_stereo,
+                        newstereo);
+                msp->detected_stereo=newstereo;
+                update = 1; 
+        }
 	if (newnicam != msp->nicam_on) {
 		update = 1;
 		dprintk("msp34xx: watch: nicam %d => %d\n",
@@ -731,18 +744,29 @@
 	wake_up_interruptible(&msp->wq);
 }
 
+static void detected_stereo_changed(struct i2c_client  *client) 
+{
+	struct msp3400c *msp = client->data;
+        int newstereo=msp->detected_stereo;
+        if(msp->stereo_event.callback) {
+                dprintk("msp34xx: calling callback oldstereo=%d, newstereo=%d \n", 
+                        msp->stereo,
+                        newstereo);
+                newstereo=msp->stereo_event.callback(msp->stereo, newstereo, msp->stereo_event.userdata);
+                dprintk("msp34xx: callback finished stereo=%d\n", 
+                        msp->stereo);
+        }
+        msp3400c_setstereo(client, newstereo);
+}
+
+
 /* stereo/multilang monitoring */
 static void watch_stereo(struct i2c_client *client)
 {
 	struct msp3400c *msp = client->data;
 
 	if (autodetect_stereo(client)) {
-		if (msp->stereo & VIDEO_SOUND_STEREO)
-			msp3400c_setstereo(client,VIDEO_SOUND_STEREO);
-		else if (msp->stereo & VIDEO_SOUND_LANG1)
-			msp3400c_setstereo(client,VIDEO_SOUND_LANG1);
-		else
-			msp3400c_setstereo(client,VIDEO_SOUND_MONO);
+                detected_stereo_changed(client);
 	}
 	if (once)
 		msp->watch_stereo = 0;
@@ -778,6 +802,10 @@
 			goto done;
 		if (debug > 1)
 			printk("msp3400: thread: sleep\n");
+		if(msp->ack != NULL) {
+				up(msp->ack); 
+				msp->ack=NULL;
+		}
 		interruptible_sleep_on(&msp->wq);
 		if (debug > 1)
 			printk("msp3400: thread: wakeup\n");
@@ -888,8 +916,9 @@
 				msp->second = carrier_detect_55[max2].cdo;
 				msp3400c_setmode(client, MSP_MODE_FM_TERRA);
 				msp->nicam_on = 0;
-				msp3400c_setstereo(client, VIDEO_SOUND_MONO);
-				msp->watch_stereo = 1;
+                                msp->detected_stereo = VIDEO_SOUND_MONO;
+                                msp3400c_setstereo(client, VIDEO_SOUND_MONO);
+                                msp->watch_stereo = 1;
 			} else if (max2 == 1 && msp->nicam) {
 				/* B/G NICAM */
 				msp->second = carrier_detect_55[max2].cdo;
@@ -915,14 +944,16 @@
 				msp->second = carrier_detect_65[max2].cdo;
 				msp3400c_setmode(client, MSP_MODE_FM_TERRA);
 				msp->nicam_on = 0;
+                                msp->detected_stereo = VIDEO_SOUND_MONO;
 				msp3400c_setstereo(client, VIDEO_SOUND_MONO);
-				msp->watch_stereo = 1;
+                                msp->watch_stereo = 1;
 			} else if (max2 == 0 &&
 				   msp->norm == VIDEO_MODE_SECAM) {
 				/* L NICAM or AM-mono */
 				msp->second = carrier_detect_65[max2].cdo;
 				msp3400c_setmode(client, MSP_MODE_AM_NICAM);
 				msp->nicam_on = 0;
+                                msp->detected_stereo = VIDEO_SOUND_MONO; 
 				msp3400c_setstereo(client, VIDEO_SOUND_MONO);
 				msp3400c_setcarrier(client, msp->second, msp->main);
 				/* volume prescale for SCART (AM mono input) */
@@ -946,7 +977,7 @@
 			msp3400c_setmode(client, MSP_MODE_FM_TERRA);
 			msp->nicam_on = 0;
 			msp3400c_setcarrier(client, msp->second, msp->main);
-			msp->stereo = VIDEO_SOUND_MONO;
+			msp->detected_stereo = VIDEO_SOUND_MONO;
 			msp3400c_setstereo(client, VIDEO_SOUND_MONO);
 			break;
 		}
@@ -1032,6 +1063,10 @@
 			goto done;
 		if (debug > 1)
 			printk("msp3410: thread: sleep\n");
+	    if(msp->ack!=NULL) {
+				up(msp->ack);
+				msp->ack=NULL;
+		}
 		interruptible_sleep_on(&msp->wq);
 		if (debug > 1)
 			printk("msp3410: thread: wakeup\n");
@@ -1155,29 +1190,29 @@
 			else
 				msp->mode = MSP_MODE_FM_NICAM2;
 			/* just turn on stereo */
-			msp->stereo = VIDEO_SOUND_STEREO;
+			msp->detected_stereo = VIDEO_SOUND_STEREO;
 			msp->nicam_on = 1;
-			msp->watch_stereo = 1;
 			msp3400c_setstereo(client,VIDEO_SOUND_STEREO);
+			msp->watch_stereo = 1;
 			break;
 		case 0x0009:			
 			msp->mode = MSP_MODE_AM_NICAM;
-			msp->stereo = VIDEO_SOUND_MONO;
 			msp->nicam_on = 1;
-			msp3400c_setstereo(client,VIDEO_SOUND_MONO);
-			msp->watch_stereo = 1;
+			msp->detected_stereo = VIDEO_SOUND_MONO;
+			msp3400c_setstereo(client, VIDEO_SOUND_MONO);
+                        msp->watch_stereo = 1;
 			break;
 		case 0x0020: /* BTSC */
 			/* just turn on stereo */
 			msp->mode   = MSP_MODE_BTSC;
-			msp->stereo = VIDEO_SOUND_STEREO;
+			msp->detected_stereo = VIDEO_SOUND_STEREO;
 			msp->nicam_on = 0;
 			msp->watch_stereo = 1;
 			msp3400c_setstereo(client,VIDEO_SOUND_STEREO);
 			break;
 		case 0x0040: /* FM radio */
 			msp->mode   = MSP_MODE_FM_RADIO;
-			msp->stereo = VIDEO_SOUND_STEREO;
+			msp->detected_stereo = VIDEO_SOUND_STEREO;
 			msp->nicam_on = 0;
 			msp->watch_stereo = 0;
 			/* scart routing */
@@ -1188,7 +1223,7 @@
 			break;
 		case 0x0003:
 			msp->mode   = MSP_MODE_FM_TERRA;
-			msp->stereo = VIDEO_SOUND_MONO;
+			msp->detected_stereo = VIDEO_SOUND_MONO;
 			msp->nicam_on = 0;
 			msp->watch_stereo = 1;
 			break;
@@ -1358,7 +1393,8 @@
 	int i;
 	
 	/* shutdown control thread */
-	del_timer(&msp->wake_stereo);
+	msp->watch_stereo=0; // make sure the timer is not re-added by the thread
+	del_timer_sync(&msp->wake_stereo);
 	if (msp->thread) 
 	{
 		msp->notify = &sem;
@@ -1367,6 +1403,7 @@
 		down(&sem);
 		msp->notify = NULL;
 	}
+        del_timer_sync(&msp->wake_stereo); // to be really sure the timer is deleted        
     	msp3400c_reset(client);
 
         /* update our own array */
@@ -1424,17 +1461,17 @@
 		switch (*sarg) {
 		case AUDIO_RADIO:
 			msp->mode   = MSP_MODE_FM_RADIO;
-			msp->stereo = VIDEO_SOUND_STEREO;
+			msp->detected_stereo = VIDEO_SOUND_STEREO;
 			msp3400c_set_scart(client,SCART_IN2,0);
 			msp3400c_write(client,I2C_MSP3400C_DFP,0x000d,0x1900);
-			msp3400c_setstereo(client,msp->stereo);
+			msp3400c_setstereo(client,VIDEO_SOUND_STEREO);
 			break;
 		case AUDIO_EXTERN:
 			msp->mode   = MSP_MODE_EXTERN;
-			msp->stereo = VIDEO_SOUND_STEREO;
+			msp->detected_stereo = VIDEO_SOUND_STEREO;
 			msp3400c_set_scart(client,SCART_IN1,0);
 			msp3400c_write(client,I2C_MSP3400C_DFP,0x000d,0x1900);
-			msp3400c_setstereo(client,msp->stereo);
+			msp3400c_setstereo(client,VIDEO_SOUND_STEREO);
 			break;
 		case AUDIO_TUNER:
 			msp->mode   = -1;
@@ -1520,7 +1557,9 @@
 		va->treble = msp->treble;
 
 		if (msp->norm != VIDEO_MODE_RADIO) {
-			autodetect_stereo(client);
+			/* alternative: autodetect_stereo(client); va->mode = msp->detected_stereo 
+                         * returning the *current* stereo mode makes more sense, though.
+                         */
 			va->mode = msp->stereo;
 		}
 		break;
@@ -1542,13 +1581,67 @@
 		msp3400c_settreble(client,msp->treble);
 
 		if (va->mode != 0) {
-			msp->watch_stereo=0;
-			del_timer(&msp->wake_stereo);
-			msp->stereo = va->mode;
 			msp3400c_setstereo(client,va->mode);
 		}
 		break;
 	}
+        case VIDIOC_G_TUNER:
+        {
+                struct v4l2_tuner *vt=(struct v4l2_tuner *)arg;
+                /* only set rxsubchans and audmode */
+
+                u32 rxsubchans=0; 
+                u32 audmode;
+
+                autodetect_stereo(client);
+                if(msp->detected_stereo&VIDEO_SOUND_STEREO)
+                        rxsubchans |= V4L2_TUNER_SUB_STEREO;
+                if(msp->detected_stereo&VIDEO_SOUND_LANG1)
+                        rxsubchans |= V4L2_TUNER_SUB_LANG1;
+                if(msp->detected_stereo&VIDEO_SOUND_LANG2)
+                        rxsubchans |= V4L2_TUNER_SUB_LANG2;
+                if(msp->detected_stereo&VIDEO_SOUND_MONO)
+                        rxsubchans |= V4L2_TUNER_SUB_MONO;
+
+                if(msp->stereo&VIDEO_SOUND_LANG2)
+                        audmode = V4L2_TUNER_MODE_LANG2;
+                else if(msp->stereo&VIDEO_SOUND_LANG1) 
+                        audmode = V4L2_TUNER_MODE_LANG1;
+                else if(msp->stereo&VIDEO_SOUND_STEREO)
+                        audmode = V4L2_TUNER_MODE_STEREO;
+                else
+                        audmode = V4L2_TUNER_MODE_MONO;
+                
+                vt->rxsubchans = rxsubchans;
+                vt->audmode    = audmode;
+                break;
+        }
+        case VIDIOC_S_TUNER:
+        {
+                struct v4l2_tuner *vt=(struct v4l2_tuner *)arg;
+                /* set mode based on audmode, ignore the rest */
+                int newstereo=0;
+                switch(vt->audmode) {
+                case V4L2_TUNER_MODE_MONO:
+                        newstereo=VIDEO_SOUND_MONO;
+                        break;
+
+                case V4L2_TUNER_MODE_STEREO:
+                        newstereo=VIDEO_SOUND_STEREO;
+                        break;
+
+                case V4L2_TUNER_MODE_LANG1:
+                        newstereo=VIDEO_SOUND_LANG1;
+                        break;
+                        
+                case V4L2_TUNER_MODE_LANG2:
+                        newstereo=VIDEO_SOUND_LANG2;
+                        break;
+                }
+                msp3400c_setstereo(client, newstereo);
+                break;
+        }
+
 	case VIDIOCSCHAN:
 	{
 		struct video_channel *vc = arg;
@@ -1556,6 +1649,8 @@
 		dprintk(KERN_DEBUG "msp34xx: VIDIOCSCHAN\n");
 		dprintk("msp34xx: switching to TV mode\n");
 		msp->norm = vc->norm;
+                msp->detected_stereo = VIDEO_SOUND_MONO;
+                detected_stereo_changed(client);
 		msp_wake_thread(client);
 		break;
 	}
@@ -1563,6 +1658,8 @@
 	{
 		/* new channel -- kick audio carrier scan */
 		dprintk(KERN_DEBUG "msp34xx: VIDIOCSFREQ\n");
+                msp->detected_stereo = VIDEO_SOUND_MONO;
+                detected_stereo_changed(client);
 		msp_wake_thread(client);
 		break;
 	}
@@ -1574,6 +1671,34 @@
 		msp3400c_set_scart(client, mspm->input, mspm->output);
 		break;
 	}
+	case MSP_STEREO_EVENT:
+	{
+		struct msp_stereo_event *ev = arg;
+		dprintk(KERN_DEBUG "msp34xx: MSP_STEREO_EVENT\n");
+		if(ev) {
+                        dprintk(KERN_DEBUG 
+                                "msp34xx: set stereo mode change callback to 0x%lx.\n", 
+                                ev->callback);
+                        memcpy(&msp->stereo_event, ev, sizeof(struct msp_stereo_event));
+		} else {
+                        dprintk(KERN_DEBUG 
+                                "msp34xx: unset stereo mode change callback.\n"); 
+                        memset(&msp->stereo_event, 0, sizeof(struct msp_stereo_event));
+		}
+		if(msp->thread) {
+				/* make sure the previous callback has finished executing
+				 * before returning
+				 */
+				struct semaphore ack_semaphore;
+				init_MUTEX_LOCKED(&ack_semaphore);
+				msp->ack=&ack_semaphore;
+				wake_up_interruptible(&msp->wq);
+				down(&ack_semaphore);
+				msp->ack=NULL; /* the thread does that, too, but I'm paranoid. */
+		}
+		break;
+	}
+
 	default:
 		/* nothing */
 		break;
Index: ivtv/driver/msp3400.h
diff -u ivtv/driver/msp3400.h:1.1 ivtv/driver/msp3400.h:1.2
--- ivtv/driver/msp3400.h:1.1	Sun Mar  7 11:26:54 2004
+++ ivtv/driver/msp3400.h	Sat Mar 20 19:12:28 2004
@@ -13,11 +13,18 @@
     int output;
 };
 
+typedef int (*t_msp_stereo_cb)(int old_stereo_mode, int new_stereo_mode, void *userdata);
+struct msp_stereo_event {
+        void *userdata;
+        t_msp_stereo_cb callback;
+};
+
 #define MSP_SET_DFPREG     _IOW('m',15,struct msp_dfpreg)
 #define MSP_GET_DFPREG     _IOW('m',16,struct msp_dfpreg)
 
 /* ioctl for MSP_SET_MATRIX will have to be registered */
 #define MSP_SET_MATRIX     _IOW('m',17,struct msp_matrix)
+#define MSP_STEREO_EVENT   _IOW('m',18,struct msp_stereo_event)
 
 #define SCART_MASK    0
 #define SCART_IN1     1
Index: ivtv/utils/test_ioctl.c
diff -u ivtv/utils/test_ioctl.c:1.1 ivtv/utils/test_ioctl.c:1.4
--- ivtv/utils/test_ioctl.c:1.1	Sun Mar  7 11:26:54 2004
+++ ivtv/utils/test_ioctl.c	Sat Mar 20 19:12:28 2004
@@ -40,6 +40,8 @@
 #define OptFrameSync    (1L<<18)
 #define OptSetDebugLevel (1L<<19)
 #define OptGetDebugLevel (1L<<20)
+#define OptSetAudioMode     (1L<<21)
+#define OptGetAudioMode     (1L<<22)
 
 /* Codec's specified */
 #define CAspect			(1L<<1)
@@ -124,6 +126,8 @@
 	printf("      contrast       =<#> Picture contrast or luma gain. [0 - 127]\n");
 	printf("      volume         =<#> Overall audio volume. [0 - 65535]\n");
 	printf("      mute           =<#> Mute audio, i. e. set the volume to zero [boolean]\n");
+	printf("  -z, --set-audiomode= Possible values: 0(mono),1(stereo),2(lang2),3(lang1)\n");
+	printf("  -Z, --get-audiomode\n");
 	printf("  -D --debug=level   sets/gets the module ivtv_debug variable\n");
  	exit(0);
 }
@@ -318,6 +322,7 @@
 	struct v4l2_frequency vf;    /* get_freq/set_freq */
 	struct v4l2_standard vs;     /* list_std */
 	struct msp_matrix mspm;      /* set_io */
+    int mode = V4L2_TUNER_MODE_STEREO; /* set audio mode */
 	int debug_level = 0;
 	struct ivtv_ioctl_framesync frameinfo;
 	int new_debug_level, gdebug_level;
@@ -353,11 +358,13 @@
 			{"set-io", 1, 0, 0},
 			{"list-ctrls", 0, 0, 0},
 			{"set-ctrl", 1, 0, 0},
+			{"set-audiomode", 1, 0, 0},
+			{"get-audiomode", 0, 0, 0},
 			{"sync", 0, 0, 0},
 			{"debug", 1, 0, 0},
 			{0, 0, 0, 0}
 		};
-		ch = getopt_long(argc, argv, ":hc:Cd:ef:g:Gmnop:q:r:stu:v:aYy:D:",
+		ch = getopt_long(argc, argv, ":hc:Cd:ef:g:Gmnop:q:r:stu:v:aYy:D:z:Z",
 				long_options, &option_index);
 		if (ch == -1)
 			break;
@@ -408,6 +415,10 @@
 				ch = 'y';
 			else if (!strcmp(long_options[option_index].name, "debug"))
 				ch = 'D';
+			else if (!strcmp(long_options[option_index].name, "set-audiomode"))
+				ch = 'z';
+			else if (!strcmp(long_options[option_index].name, "get-audiomode"))
+				ch = 'Z';
 		}
 
 		switch (ch) {
@@ -797,6 +808,13 @@
 					}
 				}
 				break;
+			case 'z':
+				set_opts |= OptSetAudioMode;
+				mode = strtol(optarg, 0L, 0);
+				break;
+			case 'Z':
+				set_opts |= OptGetAudioMode;
+				break;
 			case ':':
 				fprintf(stderr, "Option `%s' requires a value\n", argv[optind]);
 				usage();
@@ -935,7 +953,7 @@
 		else {
 			printf("Codec parameters\n");
 			printf("aspect      : %d\n", codec.aspect);
-			printf("audio       : 0x%04x\n", codec.audio_bitmask);
+			printf("audio       : 0x%08x\n", codec.audio_bitmask);
 			printf("bframes     : %d\n", codec.bframes);
 			printf("bitrate_mode: %d\n", codec.bitrate_mode);
 			printf("bitrate     : %d\n", codec.bitrate);
@@ -1110,6 +1128,43 @@
 		}
 	}
 
+	if (set_opts & OptGetAudioMode) {
+	    struct v4l2_tuner vt;
+		printf("ioctl: VIDIOC_G_TUNER\n");
+	    memset(&vt, 0, sizeof(struct v4l2_tuner));
+	    if (ioctl(fd, VIDIOC_G_TUNER, &vt) < 0) {
+		   fprintf(stderr, "ioctl: VIDIOC_G_TUNER failed\n");
+		   exit(1);
+	    }
+		printf(" current audio mode   : %d\n", vt.audmode);
+		printf("   (stereo %d, lang1 %d, lang2 %d, mono %d)\n",
+			   V4L2_TUNER_MODE_STEREO, 
+			   V4L2_TUNER_MODE_LANG1,
+			   V4L2_TUNER_MODE_LANG2,
+			   V4L2_TUNER_MODE_MONO);
+		printf(" available subchannels: 0x%x\n", vt.rxsubchans);
+		printf("   (stereo 0x%x, lang1 0x%x, lang2 0x%x, mono 0x%x)\n",
+			   V4L2_TUNER_SUB_STEREO,
+			   V4L2_TUNER_SUB_LANG1,
+			   V4L2_TUNER_SUB_LANG2,
+			   V4L2_TUNER_SUB_MONO);
+	}
+	if (set_opts & OptSetAudioMode) {
+	    struct v4l2_tuner vt;
+		printf("ioctl: VIDIOC_S_TUNER\n");
+	    memset(&vt, 0, sizeof(struct v4l2_tuner));
+	    if (ioctl(fd, VIDIOC_G_TUNER, &vt) < 0) {
+		fprintf(stderr, "ioctl: VIDIOC_G_TUNER failed\n");
+		exit(1);
+	    }
+	    vt.audmode=mode;
+	    if (ioctl(fd, VIDIOC_S_TUNER, &vt) < 0) {
+		fprintf(stderr, "ioctl: VIDIOC_S_TUNER failed\n");
+		exit(1);
+	    }
+	}
+   
+
 	close(fd);
 	exit(0);
 }

    Source: geocities.com/szermatt