Index: keys.txt
===================================================================
--- keys.txt	(.../branches/version-0.15.1)	(revision 19)
+++ keys.txt	(.../trunk)	(revision 19)
@@ -52,6 +52,7 @@
 - Left (if a jump amount is entered) to jump back that amount.
 - Right (if a jump amount is entered) to jump ahead that amount.
 - F8 to toggle the sleep timer 30m->1hr->1hr30m->2hr->Off
+- L to cycle through available languages
 
 Without the stickykeys option selected:
 
Index: libs/libmythtv/NuppelVideoPlayer.h
===================================================================
--- libs/libmythtv/NuppelVideoPlayer.h	(.../branches/version-0.15.1)	(revision 19)
+++ libs/libmythtv/NuppelVideoPlayer.h	(.../trunk)	(revision 19)
@@ -157,6 +157,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);
@@ -180,6 +181,10 @@
     void Zoom(int direction);
     int GetLetterbox(void);
 
+	bool CyclePreferredAudioStreamId(void);
+	int GetPreferredAudioStreamId(void);
+	void SetPreferredAudioStreamId(int id);
+
     void ExposeEvent(void);
 
  protected:
@@ -237,6 +242,8 @@
     void ResetCC(void);
     void UpdateCC(unsigned char *inpos);
 
+ 	void DisplayAudioChange();
+
     void UpdateTimeDisplay(void);
     void UpdateSeekAmount(bool up);
     void UpdateEditSlider(void);
@@ -381,6 +388,8 @@
 
     long long bookmarkseek;
 
+	int preferredAudioStreamId;
+
     int consecutive_blanks;
     int skipcommercials;
     int autocommercialskip;
@@ -393,6 +402,8 @@
 
     DecoderBase *decoder;
 
+	int displayStreamCount;
+
     /* avsync stuff */
     int lastaudiotime;
     int delay;
Index: libs/libmythtv/NuppelVideoPlayer.cpp
===================================================================
--- libs/libmythtv/NuppelVideoPlayer.cpp	(.../branches/version-0.15.1)	(revision 19)
+++ libs/libmythtv/NuppelVideoPlayer.cpp	(.../trunk)	(revision 19)
@@ -187,6 +187,9 @@
     warplbuff = NULL;
     warprbuff = NULL;
     warpbuffsize = 0;
+
+	preferredAudioStreamId=0;
+	displayStreamCount=0;
 }
 
 NuppelVideoPlayer::~NuppelVideoPlayer(void)
@@ -1765,6 +1768,7 @@
 
         VideoFrame *frame = videoOutput->GetLastShownFrame();
 
+		DisplayAudioChange();
         if (cc)
             ShowText();
 
@@ -1822,6 +1826,7 @@
         }
         else
         {
+			DisplayAudioChange();
             if (cc)
                 ShowText();
             videoOutput->ProcessFrame(NULL, osd, videoFilters, pipplayer);
@@ -1980,6 +1985,13 @@
             m_playbackinfo->SetBookmark(0, m_db->db());
     }
 
+ 	if(!livetv && m_db && m_playbackinfo)
+ 		preferredAudioStreamId=m_playbackinfo->GetPreferredLanguage(m_db->db());
+ 	if(preferredAudioStreamIdGetAudioStreamCount())
+ 		decoder->SetCurrentAudioStreamId(preferredAudioStreamId);
+ 	else
+ 		decoder->SetCurrentAudioStreamId(0);
+
     LoadBlankList();
     if (!blankMap.isEmpty())
     {
@@ -2214,6 +2226,102 @@
     }
 }
 
+/**
+ * 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)
+	   preferredAudioStreamId=0;
+
+   decoder->SetCurrentAudioStreamId(preferredAudioStreamId);
+
+   if(!livetv && m_db && m_playbackinfo) 
+   {
+	   QMutexLocker lockit(m_db->mutex());
+	   m_playbackinfo->SetPreferredLanguage(preferredAudioStreamId, m_db->db());
+   }
+   
+   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();
+			switch(streamId)
+			{
+				case 0: // default setting; tell the user about this capability
+					if(count>2)
+						text=QObject::tr("Multilingual");
+					else
+						text=QObject::tr("Language I/II");
+					break;
+
+				case 1: // display current setting, because 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/decoderbase.h
===================================================================
--- libs/libmythtv/decoderbase.h	(.../branches/version-0.15.1)	(revision 19)
+++ libs/libmythtv/decoderbase.h	(.../trunk)	(revision 19)
@@ -22,6 +22,7 @@
         watchingrecording = false;
         nvr_enc = NULL;
         lowbuffers = false; 
+		currentAudioStreamId=0;
     }
     virtual ~DecoderBase() { }
 
@@ -59,6 +60,23 @@
 
     virtual void SetPixelFormat(const int) { }
 
+	virtual int GetAudioStreamCount(void) { return 1; }
+	int GetCurrentAudioStreamId() 
+	{ 
+		return ValidAudioStreamId(currentAudioStreamId) ? currentAudioStreamId:0;
+	}
+	virtual void SetCurrentAudioStreamId(int audioStreamId) 
+	{ 
+		if(ValidAudioStreamId(audioStreamId))
+			currentAudioStreamId=audioStreamId;
+	}
+		
+  private:
+   bool ValidAudioStreamId(int id) 
+   {
+	   return id>=0 && idGetNumSetting("AC3PassThru", false);
+
+	dual_language=false;
 }
 
 AvFormatDecoder::~AvFormatDecoder()
@@ -1021,6 +1023,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;
@@ -1175,15 +1186,29 @@
                                                      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:
@@ -1317,6 +1342,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; sampleCyclePreferredAudioStreamId();
+	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)
+{
+	if(m_db && activenvp)
+	{
+		QString thequery = QString("REPLACE INTO channellanguage (language, channel) "
+								   "VALUES (%1, '%2')")
+			                       .arg(activenvp->GetPreferredAudioStreamId())
+             			           .arg(channel);
+		QSqlQuery query = m_db->db()->exec(thequery);
+		if(!query.isActive())
+			MythContext::DBError("WriteChannelLanguage", thequery);
+	}
+}
+void TV::RestorePreferredLanguageForChannel(QString channel)
+{
+	if(m_db && activenvp)
+	{
+		QString thequery = QString("SELECT language FROM channellanguage "
+								   "WHERE channel = '%1'").arg(channel);
+		QSqlQuery query = m_db->db()->exec(thequery);
+		if(query.isActive() && query.numRowsAffected() > 0 )
+		{
+			query.next();
+			activenvp->SetPreferredAudioStreamId(query.value(0).toInt());
+		}
+	}
+}
+
 void TV::ToggleInputs(void)
 {
     if (activenvp == nvp)
@@ -2429,8 +2494,10 @@
 
     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	(.../branches/version-0.15.1)	(revision 19)
+++ libs/libmythtv/tv_play.h	(.../trunk)	(revision 19)
@@ -154,6 +154,7 @@
     void DoToggleCC(int mode);
     void DoSkipCommercials(int direction);
     void DoEditMode(void);
+	void CycleLanguages(void);
 
     void DoQueueTranscode(void);  
 
@@ -190,6 +191,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	(.../branches/version-0.15.1)	(revision 19)
+++ libs/libmythtv/dbcheck.cpp	(.../trunk)	(revision 19)
@@ -1040,6 +1040,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)"
 ");",
@@ -1128,6 +1129,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	(.../branches/version-0.15.1)	(revision 19)
+++ libs/libmythtv/programinfo.h	(.../trunk)	(revision 19)
@@ -136,6 +136,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	(.../branches/version-0.15.1)	(revision 19)
+++ libs/libmythtv/avformatdecoder.h	(.../trunk)	(revision 19)
@@ -52,6 +52,8 @@
     bool PosMapFromDb();
     bool PosMapFromEnc();
 
+	virtual int GetAudioStreamCount(void);
+
     QString GetEncodingType(void) { return QString("MPEG-2"); }
 
     void SetPixelFormat(const int);
@@ -101,6 +103,7 @@
                       int &lower_bound, int &upper_bound);
 
     void HandleGopStart(AVPacket *pkt);
+	void FilterAudioChannel(char *samples, int len);
 
     RingBuffer *ringBuffer;
 
@@ -119,6 +122,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/programinfo.cpp
===================================================================
--- libs/libmythtv/programinfo.cpp	(.../branches/version-0.15.1)	(revision 19)
+++ libs/libmythtv/programinfo.cpp	(.../trunk)	(revision 19)
@@ -1017,6 +1017,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	(.../branches/version-0.15.1)	(revision 19)
+++ libs/libavcodec/mpegaudiodec.c	(.../trunk)	(revision 19)
@@ -1256,6 +1256,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	(.../branches/version-0.15.1)	(revision 19)
+++ libs/libavcodec/avcodec.h	(.../trunk)	(revision 19)
@@ -722,6 +722,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