Index: keys.txt
===================================================================
--- keys.txt	(.../version-0.14)	(revision 19)
+++ keys.txt	(.../last-0.14)	(revision 19)
@@ -47,6 +47,7 @@
 - W to cycle through 4:3 aspect ratio, 16:9, and 4:3 Zoom (like pan and scan)
 - Left (if a jump amount is entered) to jump back that amount.
 - Right (if a jump amount is entered) to jump ahead that amount.
+- L to cycle through available languages
 
 Without the stickykeys option selected:
 
Index: libs/libmythtv/NuppelVideoPlayer.h
===================================================================
--- libs/libmythtv/NuppelVideoPlayer.h	(.../version-0.14)	(revision 19)
+++ libs/libmythtv/NuppelVideoPlayer.h	(.../last-0.14)	(revision 19)
@@ -155,6 +155,7 @@
     void DrawSlice(VideoFrame *frame, int x, int y, int w, int h);
 
     bool GetRawAudioState(void);
+	void AudioStreamCountChanged(int oldcount, int newcount);
     void AddAudioData(char *buffer, int len, long long timecode);
     void AddAudioData(short int *lbuffer, short int *rbuffer, int samples,
                       long long timecode);
@@ -177,6 +178,10 @@
     void ToggleLetterbox(void);
     int GetLetterbox(void);
 
+	bool CyclePreferredAudioStreamId(void);
+	int GetPreferredAudioStreamId(void);
+	void SetPreferredAudioStreamId(int id);
+
     void ExposeEvent(void);
 
  protected:
@@ -231,6 +236,7 @@
     int UpdateDelay(struct timeval *nexttrigger);
 
     void ShowText();
+	void DisplayAudioChange();
 
     void UpdateTimeDisplay(void);
     void UpdateSeekAmount(bool up);
@@ -372,6 +378,8 @@
 
     long long bookmarkseek;
 
+	int preferredAudioStreamId;
+
     int consecutive_blanks;
     int skipcommercials;
     int autocommercialskip;
@@ -382,8 +390,10 @@
     int ccindent[4];
     int lastccrow;
 
-    DecoderBase *decoder;
+	DecoderBase *decoder;
 
+	int displayStreamCount;
+
     /* avsync stuff */
     int lastaudiotime;
     int delay;
Index: libs/libmythtv/NuppelVideoPlayer.cpp
===================================================================
--- libs/libmythtv/NuppelVideoPlayer.cpp	(.../version-0.14)	(revision 19)
+++ libs/libmythtv/NuppelVideoPlayer.cpp	(.../last-0.14)	(revision 19)
@@ -178,6 +178,8 @@
     warplbuff = NULL;
     warprbuff = NULL;
     warpbuffsize = 0;
+
+	preferredAudioStreamId=0;
 }
 
 NuppelVideoPlayer::~NuppelVideoPlayer(void)
@@ -1583,6 +1585,7 @@
 
         VideoFrame *frame = videoOutput->GetLastShownFrame();
 
+		DisplayAudioChange();
         if (cc)
             ShowText();
 
@@ -1640,6 +1643,7 @@
         }
         else
         {
+			DisplayAudioChange();
             if (cc)
                 ShowText();
             videoOutput->ProcessFrame(NULL, osd, videoFilters, pipplayer);
@@ -1762,8 +1766,16 @@
 
         if (gContext->GetNumSetting("ClearSavedPosition", 1))
             m_playbackinfo->SetBookmark(0, m_db);
+
     }
 
+	if(!livetv && m_db && m_playbackinfo)
+		preferredAudioStreamId=m_playbackinfo->GetPreferredLanguage(m_db);
+	if(preferredAudioStreamIdGetAudioStreamCount())
+		decoder->SetCurrentAudioStreamId(preferredAudioStreamId);
+	else
+		decoder->SetCurrentAudioStreamId(0);
+
     LoadBlankList();
     if (!blankMap.isEmpty())
     {
@@ -1999,6 +2011,109 @@
     }
 }
 
+/**
+ * Cycle through all currently available streams.
+ * Returns true if something was changed.
+ */
+bool NuppelVideoPlayer::CyclePreferredAudioStreamId()
+{
+   int count = decoder->GetAudioStreamCount();	
+   if(count<=1)
+	   return false; 
+
+   preferredAudioStreamId++;
+   if(preferredAudioStreamId>=count || preferredAudioStreamId<0)
+   {
+	   printf("DEBUG: preferredAudioStreamId reset to 0 was %d\n", preferredAudioStreamId);
+	   preferredAudioStreamId=0;
+
+   }
+   decoder->SetCurrentAudioStreamId(preferredAudioStreamId);
+
+   if(!livetv && m_db && m_playbackinfo) 
+   {
+	   QMutexLocker lockit(&db_lock);
+	   printf("DEBUG: set preferred language to %d\n", preferredAudioStreamId);
+	   m_playbackinfo->SetPreferredLanguage(preferredAudioStreamId, m_db);
+   }
+   else
+	   printf("DEBUG: do not set preferred language to %d\n", count);
+   
+   return true;
+}
+
+int NuppelVideoPlayer::GetPreferredAudioStreamId()
+{
+	return preferredAudioStreamId;
+}
+void NuppelVideoPlayer::SetPreferredAudioStreamId(int id)
+{
+	preferredAudioStreamId=id;
+	if(preferredAudioStreamIdGetAudioStreamCount())
+		decoder->SetCurrentAudioStreamId(preferredAudioStreamId);
+	else
+		decoder->SetCurrentAudioStreamId(0);
+}
+/**
+ * This function is for the audio decoder to tell the player
+ * the number of streams has changed. The player can then
+ * choose to make the user aware of this fact and/or fallback
+ * to a default stream. This is run on the decoder thread.
+ * See also DisplayAudioChange()
+ */
+void NuppelVideoPlayer::AudioStreamCountChanged(int oldcount, int newcount)
+{
+	if(!decoder)
+		return;
+   int current = decoder->GetCurrentAudioStreamId();
+   if(current>=newcount)
+	   decoder->SetCurrentAudioStreamId(0);
+   if(preferredAudioStreamIdSetCurrentAudioStreamId(preferredAudioStreamId);
+   
+   if(newcount>1 && newcount>oldcount)
+	   displayStreamCount=newcount;
+   else
+	   displayStreamCount=0;
+}
+
+/**
+ * Tell the user about an increase in the number
+ * of audio streams. This is run on the display thread.
+ */
+void NuppelVideoPlayer::DisplayAudioChange()
+{
+	int count = displayStreamCount;
+	if(count!=0)
+	{
+		displayStreamCount=0;
+		if(!disableaudio && osd)
+		{
+			QString text;
+			int streamId = decoder->GetCurrentAudioStreamId();
+			printf("DEBUG: current audio stream: %d\n", streamId);
+			switch(streamId)
+			{
+				case 0: // tell the user about this capability
+					if(count==2)
+						text=QObject::tr("Language I/II");
+					else 
+						text=QObject::tr("Multilingual");
+					break;
+
+				case 1: // display current setting; language has changed
+					text=QObject::tr("Language II");
+					break;
+
+				default:
+					text=QObject::tr("Language %1 %").arg(streamId+1);
+					break;
+			}
+			osd->SetSettingsText(text, 4);
+		}
+	}
+}
+
 void NuppelVideoPlayer::AddAudioData(short int *lbuffer, short int *rbuffer,
                                      int samples, long long timecode)
 {
Index: libs/libmythtv/avformatdecoder.cpp
===================================================================
--- libs/libmythtv/avformatdecoder.cpp	(.../version-0.14)	(revision 19)
+++ libs/libmythtv/avformatdecoder.cpp	(.../last-0.14)	(revision 19)
@@ -64,6 +64,8 @@
     memset(prvpkt, 0, 3);
 
     do_ac3_passthru = gContext->GetNumSetting("AC3PassThru", false);
+
+	dual_language=true;
 }
 
 AvFormatDecoder::~AvFormatDecoder()
@@ -970,6 +972,15 @@
     return retval;
 }
 
+int AvFormatDecoder::GetAudioStreamCount(void)
+{
+   if(dual_language && audio_channels==2)
+	  return 2;
+   else if(audio_channels>0)
+	  return 1;
+   return 0;
+}
+
 void AvFormatDecoder::GetFrame(int onlyvideo)
 {
     AVPacket *pkt = NULL;
@@ -1120,19 +1131,34 @@
                             audio_sample_size = audio_channels * 2;
 
                             m_parent->SetEffDsp(audio_sampling_rate * 100);
-                            m_parent->SetAudioParams(16, audio_channels,
+                            m_parent->SetAudioParams(16, 
+													 audio_channels,
                                                      audio_sampling_rate);
                             m_parent->ReinitAudio();
                         }
+						if(curstream->codec.avcodec_dual_language!=dual_language)
+						{
+							if(dual_language) 
+							{
+								dual_language=false;
+								m_parent->AudioStreamCountChanged(2, 1);
+							}
+							else
+							{
+								dual_language=true;
+								m_parent->AudioStreamCountChanged(1, 2);
+							}
+						}
                     }
 
                     long long temppts = lastapts;
                     // calc for next frame
                     lastapts += (long long)((double)(data_size * 1000) /
                                 audio_sample_size / audio_sampling_rate);
-
-                    m_parent->AddAudioData((char *)samples, data_size, 
-                                           temppts);
+					if(dual_language & audio_channels==2)
+						FilterAudioChannel((char *)samples, data_size);
+					m_parent->AddAudioData((char *)samples, data_size, 
+										   temppts);
                     break;
                 }
                 case CODEC_TYPE_VIDEO:
@@ -1266,6 +1292,38 @@
     m_parent->SetFramesPlayed(framesPlayed);
 }
 
+/**
+ * Filter out either the right or the left channel, based
+ * on currentAudioStreamId (0=left, 1=right)
+ */
+void AvFormatDecoder::FilterAudioChannel(char *buffer, int samples)
+{
+	const int channel = currentAudioStreamId==0 ? 0:1;
+	const int bits = 16;
+	int bit;
+	const char *from;
+	char *to;
+	const int samplesize = (bits/8)*2;
+	const int halfsample = bits/8;
+	int sample;
+	if(channel==0)
+	{
+		from=buffer;
+		to=buffer+halfsample;
+	}
+	else 
+	{
+		from=buffer+halfsample;
+		to=buffer;
+	}
+	
+	for(sample=0; sample=0 && idstart(kMuteTimeout, true);
 }
 
+void TV::CycleLanguages(void)
+{
+	if(!activenvp)
+		return;
+	bool changed = activenvp->CyclePreferredAudioStreamId();
+	if(osd && !browsemode)
+	{
+		QString text;
+		if(changed)
+		{
+			int current = activenvp->GetPreferredAudioStreamId();
+			switch(current)
+			{
+				case 0:
+					text=tr("Language I");
+					break;
+
+				case 1:
+					text=tr("Language II");
+					break;
+					
+				default:
+					text=QString( tr("Language %1 %") ).arg(current+1);
+					break;
+			}
+		}
+		else 
+		{
+			text=tr("Monolingual");
+		}
+		osd->SetSettingsText(text, 3);
+	}
+}
+
+void TV::RecordPreferredLanguageForChannel(QString channel)
+{
+	printf("DEBUG: RecordPreferredLanguageForChannel(%s)\n", channel.ascii());
+	if(m_db && activenvp)
+	{
+		QString thequery = QString("REPLACE INTO channellanguage (language, channel) "
+								   "VALUES (%1, '%2')")
+			                       .arg(activenvp->GetPreferredAudioStreamId())
+             			           .arg(channel);
+		printf("DEBUG: %s\n", thequery.ascii());
+		QSqlQuery query = m_db->exec(thequery);
+		if(!query.isActive())
+			MythContext::DBError("WriteChannelLanguage", thequery);
+	}
+}
+void TV::RestorePreferredLanguageForChannel(QString channel)
+{
+	printf("DEBUG: RestorePreferredLanguageForChannel(%s)\n", channel.ascii());
+
+	if(m_db && activenvp)
+	{
+		QString thequery = QString("SELECT language FROM channellanguage "
+								   "WHERE channel = '%1'").arg(channel);
+		printf("DEBUG: %s\n", thequery.ascii());
+		QSqlQuery query = m_db->exec(thequery);
+		if(query.isActive() && query.numRowsAffected() > 0 )
+		{
+			query.next();
+			activenvp->SetPreferredAudioStreamId(query.value(0).toInt());
+		}
+	}
+}
+
 void TV::ToggleInputs(void)
 {
     if (activenvp == nvp)
@@ -2156,14 +2226,18 @@
     // all we care about is the ringbuffer being paused, here..
     activerbuffer->WaitForPause();
 
+
+
     // Save the current channel if this is the first time
     if (channame_vector.size() == 0)
         AddPreviousChannel();
 
     activerecorder->Pause();
     activerbuffer->Reset();
+	RecordPreferredLanguageForChannel(activerecorder->GetCurrentChannel());
     activerecorder->SetChannel(name);
 
+	RestorePreferredLanguageForChannel(name);
     activenvp->ResetPlaying();
     activenvp->Play(1.0, true, false);
 
Index: libs/libmythtv/tv_play.h
===================================================================
--- libs/libmythtv/tv_play.h	(.../version-0.14)	(revision 19)
+++ libs/libmythtv/tv_play.h	(.../last-0.14)	(revision 19)
@@ -139,6 +139,7 @@
     void ChangeFFRew(int direction);
     void RepeatFFRew(void);
     void DoSkipCommercials(int direction);
+	void CycleLanguages(void);
 
     void DoQueueTranscode(void);  
  
@@ -170,6 +171,9 @@
     void ToggleRecord(void);
     void BrowseChannel(QString &chan);
 
+	void RecordPreferredLanguageForChannel(QString channel);
+	void RestorePreferredLanguageForChannel(QString channel);
+
     void DoTogglePictureAttribute(void);
     void DoToggleRecPictureAttribute(void);
     void DoChangePictureAttribute(int control, bool up, bool rec);
Index: libs/libmythtv/dbcheck.cpp
===================================================================
--- libs/libmythtv/dbcheck.cpp	(.../version-0.14)	(revision 19)
+++ libs/libmythtv/dbcheck.cpp	(.../last-0.14)	(revision 19)
@@ -681,6 +681,7 @@
 "    editing INT UNSIGNED NOT NULL DEFAULT 0,"
 "    cutlist TEXT NULL,"
 "    autoexpire INT DEFAULT 0 NOT NULL,"
+"    language INT UNSIGNED NOT NULL DEFAULT 0,"
 "    PRIMARY KEY (chanid, starttime),"
 "    INDEX (endtime)"
 ");",
@@ -769,6 +770,10 @@
 "    chanid int(11) unsigned NOT NULL default '0',"
 "    PRIMARY KEY (favid)"
 ");",
+"CREATE TABLE IF NOT EXISTS channellanguage ("
+"    channel VARCHAR(20) NOT NULL PRIMARY KEY,"
+"    language INT unsigned NOT NULL"
+");",
 "CREATE TABLE IF NOT EXISTS recordedmarkup"
 "("
 "    chanid INT UNSIGNED NOT NULL,"
Index: libs/libmythtv/programinfo.h
===================================================================
--- libs/libmythtv/programinfo.h	(.../version-0.14)	(revision 19)
+++ libs/libmythtv/programinfo.h	(.../last-0.14)	(revision 19)
@@ -128,6 +128,8 @@
 
     void SetBookmark(long long pos, QSqlDatabase *db);
     long long GetBookmark(QSqlDatabase *db);
+	void SetPreferredLanguage(int language, QSqlDatabase *db);
+	int GetPreferredLanguage(QSqlDatabase *db);
     bool IsEditing(QSqlDatabase *db);
     void SetEditing(bool edit, QSqlDatabase *db);
     bool IsCommFlagged(QSqlDatabase *db);
Index: libs/libmythtv/avformatdecoder.h
===================================================================
--- libs/libmythtv/avformatdecoder.h	(.../version-0.14)	(revision 19)
+++ libs/libmythtv/avformatdecoder.h	(.../last-0.14)	(revision 19)
@@ -52,6 +52,8 @@
     bool PosMapFromDb();
     bool PosMapFromEnc();
 
+	virtual int GetAudioStreamCount(void);
+
     QString GetEncodingType(void) { return QString("MPEG-2"); }
 
   protected:
@@ -99,6 +101,7 @@
                       int &lower_bound, int &upper_bound);
 
     void HandleGopStart(AVPacket *pkt);
+	void FilterAudioChannel(char *samples, int len);
 
     RingBuffer *ringBuffer;
 
@@ -117,6 +120,7 @@
     int audio_sample_size;
     int audio_sampling_rate;
     int audio_channels;
+	bool dual_language;
 
     int audio_check_1st;
     int audio_sampling_rate_2nd;
Index: libs/libmythtv/mpegrecorder.cpp
===================================================================
--- libs/libmythtv/mpegrecorder.cpp	(.../version-0.14)	(revision 19)
+++ libs/libmythtv/mpegrecorder.cpp	(.../last-0.14)	(revision 19)
@@ -307,6 +307,7 @@
     if (ioctl(chanfd, VIDIOC_S_CTRL, &ctrl) < 0)
     {
         cerr << "Error setting codec params\n";
+        printf("VIDIOC_S_CTRL failed (0x%lx)\n", VIDIOC_S_CTRL);
         perror("VIDIOC_S_CTRL:");
         return;
     }
Index: libs/libmythtv/programinfo.cpp
===================================================================
--- libs/libmythtv/programinfo.cpp	(.../version-0.14)	(revision 19)
+++ libs/libmythtv/programinfo.cpp	(.../last-0.14)	(revision 19)
@@ -923,6 +923,48 @@
     return pos;
 }
 
+void ProgramInfo::SetPreferredLanguage(int language, QSqlDatabase *db)
+{
+    MythContext::KickDatabase(db);
+
+    QString starts = recstartts.toString("yyyyMMddhhmm");
+    starts += "00";
+
+    QString querystr;
+
+	querystr = QString("UPDATE recorded SET language = %1, starttime = '%2' "
+					   "WHERE chanid = '%3' AND "
+					   "starttime = '%4';").arg(language).arg(starts).arg(chanid).arg(starts);
+
+    QSqlQuery query = db->exec(querystr);
+    if (!query.isActive())
+        MythContext::DBError("Save language update", querystr);
+}
+
+int ProgramInfo::GetPreferredLanguage(QSqlDatabase *db)
+{
+    MythContext::KickDatabase(db);
+
+    QString starts = recstartts.toString("yyyyMMddhhmm");
+    starts += "00";
+
+    QString querystr = QString("SELECT language FROM recorded WHERE "
+                               "chanid = '%1' AND starttime = '%2';")
+                              .arg(chanid).arg(starts);
+
+    QSqlQuery query = db->exec(querystr);
+    if (query.isActive() && query.numRowsAffected() > 0)
+    {
+        query.next();
+
+        QVariant value = query.value(0);
+		if(!value.isNull())
+			return value.toInt();
+    }
+	return 0;
+}
+
+
 bool ProgramInfo::IsEditing(QSqlDatabase *db)
 {
     MythContext::KickDatabase(db);
Index: libs/libavcodec/mpegaudiodec.c
===================================================================
--- libs/libavcodec/mpegaudiodec.c	(.../version-0.14)	(revision 19)
+++ libs/libavcodec/mpegaudiodec.c	(.../last-0.14)	(revision 19)
@@ -1255,6 +1255,7 @@
         break;
     }
 
+	avctx->avcodec_dual_language = s->mode==MPA_DUAL ? 1:0;
     avctx->sample_rate = s->sample_rate;
     avctx->channels = s->nb_channels;
     avctx->bit_rate = s->bit_rate;
Index: libs/libavcodec/avcodec.h
===================================================================
--- libs/libavcodec/avcodec.h	(.../version-0.14)	(revision 19)
+++ libs/libavcodec/avcodec.h	(.../last-0.14)	(revision 19)
@@ -679,6 +679,7 @@
     int sample_fmt;  ///< sample format, currenly unused 
 
     /* the following data should not be initialized */
+
     int frame_size;     ///< in samples, initialized when calling 'init' 
     int frame_number;   ///< audio or video frame number 
     int real_pict_num;  ///< returns the real picture number of previous encoded frame 
@@ -690,6 +691,21 @@
      * - decoding: unused
      */
     int delay;
+		
+   /** 
+	* set when bilingual audio data has been detected.
+	* 0 normally, 1 if dual language flag is set
+	* 
+	* this is a hack to keep binary compatibility with
+	* old versions of the library. where else could I
+	* put this flag ? it's such a little flag... one
+	* miserable, orphaned bit rejected by everyone...
+	* it's so unfair!
+	*
+	* - encoding: unused (called delay in this case...)
+	* - decoding: set by lavc
+	*/
+#define avcodec_dual_language delay
     
     /* - encoding parameters */
     float qcompress;  ///< amount of qscale change between easy & hard scenes (0.0-1.0)

    Source: geocities.com/szermatt