Index: libs/libmythtv/decoderbase.h =================================================================== --- libs/libmythtv/decoderbase.h (revision 11213) +++ libs/libmythtv/decoderbase.h (working copy) @@ -21,13 +21,18 @@ stream_id(-1), easy_reader(false), wide_aspect_ratio(false) {} StreamInfo(int a, int b, uint c, int d, bool e = false, bool f = false) : av_stream_index(a), language(b), language_index(c), stream_id(d), - easy_reader(e), wide_aspect_ratio(f) {} + av_substream_index(-1), easy_reader(e), wide_aspect_ratio(f) {} + // Audio stream/substream constructor + StreamInfo(int a, int b, uint c, int d, int e) : + av_stream_index(a), language(b), language_index(c), stream_id(d), + av_substream_index(e), easy_reader(false), wide_aspect_ratio(false) {} public: int av_stream_index; int language; ///< ISO639 canonical language key uint language_index; int stream_id; + int av_substream_index; // -1 = no substream, 0 for first dual audio stream bool easy_reader; bool wide_aspect_ratio; Index: libs/libmythtv/avformatdecoder.cpp =================================================================== --- libs/libmythtv/avformatdecoder.cpp (revision 11213) +++ libs/libmythtv/avformatdecoder.cpp (working copy) @@ -1468,8 +1468,18 @@ lang_indx = lang_aud_cnt[lang]; lang_aud_cnt[lang]++; } - tracks[kTrackTypeAudio].push_back( - StreamInfo(i, lang, lang_indx, ic->streams[i]->id)); + if (ic->streams[i]->codec->avcodec_dual_language) + { + tracks[kTrackTypeAudio].push_back( + StreamInfo(i, lang, lang_indx, ic->streams[i]->id, 0)); + tracks[kTrackTypeAudio].push_back( + StreamInfo(i, lang, lang_indx, ic->streams[i]->id, 1)); + } + else + { + tracks[kTrackTypeAudio].push_back( + StreamInfo(i, lang, lang_indx, ic->streams[i]->id)); + } VERBOSE(VB_AUDIO, LOC + QString( "Audio Track #%1 is A/V stream #%2 " @@ -1533,6 +1543,64 @@ return scanerror; } +/** \fn AvFormatDecoder::FixAudioStreamSubIndexes(bool, bool) + * \brief Reacts to DUAL/STEREO changes on the fly and fix streams. + * + * This function kicks in when a switch between dual and stereo + * mpeg audio is detected. Such changes can and will happen at + * any time. + * + * It can only be used in the case where the mpeg file contains + * exactly one audio stream that switches automatically between + * stereo (1 language) and dual (2 unspecified languages) audio. + * + * If this method returns true, the stream list has changed and + * a new audio stream needs to be selected, usually using + * AvFormatDecoder::autoSelectSubtitleTrack() + * + * \param wasDual true if the stream used to contain dual audio + * \param isDual true if the stream currently contains dual audio + * \return true if a change was made + */ +bool AvFormatDecoder::FixAudioStreamSubIndexes(bool wasDual, bool isDual) +{ + if (wasDual == isDual) + { + // No changes + return false; + } + + QMutexLocker locker(&avcodeclock); + int numStreams = tracks[kTrackTypeAudio].size(); + if (isDual) + { + assert(numStreams == 1); + + // When assertions are off: ignore change + if (numStreams != 1) + return false; + + // Split stream in two (Language I + Language II) + tracks[kTrackTypeAudio].push_back(tracks[kTrackTypeAudio][0]); + tracks[kTrackTypeAudio][0].av_substream_index = 0; + tracks[kTrackTypeAudio][1].av_substream_index = 1; + } + else + { + assert(numStreams == 2); + + if (numStreams != 2) + return false; + + // Remove extra stream + tracks[kTrackTypeAudio][0].av_substream_index = -1; + tracks[kTrackTypeAudio].pop_back(); + } + + // Changed + return true; +} + int get_avf_buffer(struct AVCodecContext *c, AVFrame *pic) { AvFormatDecoder *nd = (AvFormatDecoder *)(c->opaque); @@ -2346,7 +2414,9 @@ * preference: * * 1) The stream last selected by the user, which is - * recalled as the Nth stream in the preferred language. + * recalled as the Nth stream in the preferred language + * or the Nth substream when audio is in dual language + * format (each channel contains a different language track) * If it can not be located we attempt to find a stream * in the same language. * @@ -2413,6 +2483,24 @@ int selTrack = (1 == numStreams) ? 0 : -1; int wlang = wtrack.language; + + if (selTrack < 0 && wtrack.av_substream_index >=0) + { + VERBOSE(VB_AUDIO, LOC + "Trying to reselect audio sub-stream"); + // Dual stream without language information: choose + // the previous (sub)stream that was kept in wtrack. + int substream_index = wtrack.av_substream_index; + + for (uint i = 0; i < numStreams; i++) + { + if (atracks[i].av_substream_index == substream_index) + { + selTrack = i; + break; + } + } + } + if ((selTrack < 0) && wlang >= -1 && numStreams) { VERBOSE(VB_AUDIO, LOC + "Trying to reselect audio track"); @@ -2493,6 +2581,40 @@ return selTrack; } +static void filter_audio_data(int channel, AudioInfo* audioInfo, + char *buffer, int bufsize) +{ + // Only stereo -> mono (left or right) is supported + assert(audioInfo->channels == 2); + assert(channel == 0 || channel == 1); + + const int samplesize = audioInfo->sample_size; + const int samples = bufsize / samplesize; + const int halfsample = samplesize/2; + + const char *from; + char *to; + + if (channel==0) + { + from=buffer; + to=buffer+halfsample; + } + else + { + from=buffer+halfsample; + to=buffer; + } + + for (int sample = 0; sample < samples; + sample++, from += samplesize, to += samplesize) + { + for(int bit=0; bit < halfsample; bit++) + to[bit]=from[bit]; + } +} + + bool AvFormatDecoder::GetFrame(int onlyvideo) { AVPacket *pkt = NULL; @@ -2761,6 +2883,7 @@ avcodeclock.lock(); int ctype = curstream->codec->codec_type; int audIdx = selectedTrack[kTrackTypeAudio].av_stream_index; + int audSubIdx = selectedTrack[kTrackTypeAudio].av_substream_index; int subIdx = selectedTrack[kTrackTypeSubtitle].av_stream_index; avcodeclock.unlock(); @@ -2770,6 +2893,15 @@ { case CODEC_TYPE_AUDIO: { + bool reselectAudioTrack = false; + + // detect switches between stereo and dual languages + if (FixAudioStreamSubIndexes(audSubIdx != -1, + curstream->codec->avcodec_dual_language)) + { + reselectAudioTrack = true; + } + // detect channels on streams that need // to be decoded before we can know this if (!curstream->codec->channels) @@ -2781,16 +2913,26 @@ &data_size, ptr, len); if (curstream->codec->channels) { - currentTrack[kTrackTypeAudio] = -1; - selectedTrack[kTrackTypeAudio] - .av_stream_index = -1; - audIdx = -1; - AutoSelectAudioTrack(); - audIdx = selectedTrack[kTrackTypeAudio] - .av_stream_index; + reselectAudioTrack = true; } } + if (reselectAudioTrack) + { + QMutexLocker locker(&avcodeclock); + currentTrack[kTrackTypeAudio] = -1; + selectedTrack[kTrackTypeAudio] + .av_stream_index = -1; + audIdx = -1; + audSubIdx = -1; + AutoSelectAudioTrack(); + audIdx = selectedTrack[kTrackTypeAudio] + .av_stream_index; + audSubIdx = selectedTrack[kTrackTypeAudio] + .av_substream_index; + } + + if (firstloop && pkt->pts != (int64_t)AV_NOPTS_VALUE) lastapts = (long long)(av_q2d(curstream->time_base) * pkt->pts * 1000); @@ -2873,6 +3015,11 @@ .arg(pkt->pts).arg(pkt->dts).arg(temppts) .arg(lastapts)); + if (audSubIdx != -1) + { + filter_audio_data(audSubIdx, &audioOut, + (char *)audioSamples, data_size); + } GetNVP()->AddAudioData((char *)audioSamples, data_size, temppts); Index: libs/libmythtv/mpegrecorder.h =================================================================== --- libs/libmythtv/mpegrecorder.h (revision 11213) +++ libs/libmythtv/mpegrecorder.h (working copy) @@ -77,6 +77,7 @@ int bitrate, maxbitrate, streamtype, aspectratio; int audtype, audsamplerate, audbitratel1, audbitratel2; int audvolume; + int language; // Input file descriptors int chanfd; @@ -101,6 +102,7 @@ static const int audRateL1[]; static const int audRateL2[]; + static const char* languages[]; static const char *streamType[]; static const char *aspectRatio[]; static const unsigned int kBuildBufferMaxSize; Index: libs/libmythtv/avformatdecoder.h =================================================================== --- libs/libmythtv/avformatdecoder.h (revision 11213) +++ libs/libmythtv/avformatdecoder.h (working copy) @@ -184,6 +184,7 @@ void SeekReset(long long, uint skipFrames, bool doFlush, bool discardFrames); bool SetupAudioStream(void); + bool FixAudioStreamSubIndexes(bool wasDual, bool isDual); /// Update our position map, keyframe distance, and the like. /// Called for key frame packets. Index: libs/libmythtv/mpegrecorder.cpp =================================================================== --- libs/libmythtv/mpegrecorder.cpp (revision 11213) +++ libs/libmythtv/mpegrecorder.cpp (working copy) @@ -65,6 +65,11 @@ "SVCD", "DVD-Special 1", "DVD-Special 2", 0 }; +const char* MpegRecorder::languages[] = +{ + "Language I", "Language II", "Dual", 0 +}; + const char* MpegRecorder::aspectRatio[] = { "Square", "4:3", "16:9", "2.21:1", 0 @@ -90,7 +95,7 @@ streamtype(0), aspectratio(2), audtype(2), audsamplerate(48000), audbitratel1(14), audbitratel2(14), - audvolume(80), + audvolume(80), language(0), // Input file descriptors chanfd(-1), readfd(-1), // Keyframe tracking inforamtion @@ -201,6 +206,22 @@ QString("%1 is invalid").arg(value)); } } + else if (opt == "mpeg2language") + { + bool found = false; + for (unsigned int i = 0; i < sizeof(languages) / sizeof(char*); i++) + { + if (QString(languages[i]) == value) + { + language = i; + found = true; + break; + } + } + + if (!found) + cerr << "MPEG2 language (stereo) flag : " << value << " is invalid\n"; + } else if (opt == "mpeg2aspectratio") { bool found = false; @@ -268,6 +289,8 @@ profile->byName("mpeg2streamtype")->getValue()); SetOption("mpeg2aspectratio", profile->byName("mpeg2aspectratio")->getValue()); + SetOption("mpeg2language", + profile->byName("mpeg2language")->getValue()); SetIntOption(profile, "samplerate"); SetOption("mpeg2audtype", profile->byName("mpeg2audtype")->getValue()); @@ -322,6 +345,45 @@ return false; } + struct v4l2_tuner vt; + memset(&vt, 0, sizeof(struct v4l2_tuner)); + if (ioctl(chanfd, VIDIOC_G_TUNER, &vt) < 0) + { + cerr << "Error getting tuner settings\n"; + perror("VIDIOC_G_TUNER:"); + return false; + } + + switch(language) + { + case 0: + default: /* just in case */ + vt.audmode=V4L2_TUNER_MODE_LANG1; + break; + + case 1: + vt.audmode=V4L2_TUNER_MODE_LANG2; + break; + + case 2: + if(audtype != 1) + vt.audmode=V4L2_TUNER_MODE_STEREO; + else + { + vt.audmode=V4L2_TUNER_MODE_LANG1; + cerr << "Dual audio mode incompatible with Layer I audio, falling back to Language I (mpeg2language)\n"; + } + break; + } + + if (ioctl(chanfd, VIDIOC_S_TUNER, &vt) < 0) + { + cerr << "Error setting tuner audio mode\n"; + perror("VIDIOC_S_TUNER:"); + return false; + } + + struct v4l2_control ctrl; ctrl.id = V4L2_CID_AUDIO_VOLUME; ctrl.value = 65536 / 100 *audvolume; Index: libs/libmythtv/recordingprofile.cpp =================================================================== --- libs/libmythtv/recordingprofile.cpp (revision 11213) +++ libs/libmythtv/recordingprofile.cpp (working copy) @@ -214,6 +214,26 @@ }; }; +class MPEG2language: public CodecParam, public ComboBoxSetting { + public: + MPEG2language(const RecordingProfile& parent): + CodecParam(parent, "mpeg2language") { + setLabel(QObject::tr("SAP/Bilingual")); + + addSelection("Language I"); + addSelection("Language II"); + addSelection("Dual"); + setValue(0); + setHelpText(QObject::tr("Chooses the language(s) to record when " + "two languages are broadcast. Only Layer II " + "supports the recording of two languages (Dual). " + "Version 0.1.10+ of ivtv driver is required for this " + "setting to have any effect.")); + }; +}; + + + class AudioCompressionSettings: public VerticalConfigurationGroup, public TriggeredConfigurationGroup { public: @@ -243,6 +263,7 @@ params->setLabel("MPEG-2 Hardware Encoder"); params->addChild(new SampleRate(parent, false)); params->addChild(new MPEG2AudioBitrateSettings(parent)); + params->addChild(new MPEG2language(parent)); params->addChild(new MPEG2audVolume(parent)); addTarget("MPEG-2 Hardware Encoder", params); Index: libs/libavcodec/mpegaudiodec.c =================================================================== --- libs/libavcodec/mpegaudiodec.c (revision 11213) +++ libs/libavcodec/mpegaudiodec.c (working copy) @@ -1269,6 +1269,7 @@ avctx->sample_rate = s->sample_rate; avctx->channels = s->nb_channels; + avctx->avcodec_dual_language = s->mode == MPA_DUAL ? 1 : 0; avctx->bit_rate = s->bit_rate; avctx->sub_id = s->layer; return s->frame_size; Index: libs/libavcodec/avcodec.h =================================================================== --- libs/libavcodec/avcodec.h (revision 11213) +++ libs/libavcodec/avcodec.h (working copy) @@ -823,6 +823,20 @@ */ 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. + * + * - 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) float qblur; ///< amount of qscale smoothing over time (0.0-1.0)