Index: libs/libmythtv/avformatdecoder.cpp =================================================================== --- libs/libmythtv/avformatdecoder.cpp (revision 11213) +++ libs/libmythtv/avformatdecoder.cpp (working copy) @@ -73,7 +73,7 @@ AvFormatDecoderPrivate(bool allow_libmpeg2) : mpeg2dec(NULL), allow_mpeg2dec(allow_libmpeg2) { ; } ~AvFormatDecoderPrivate() { DestroyMPEG2(); } - + bool InitMPEG2(); bool HasMPEG2Dec() const { return (bool)(mpeg2dec); } @@ -213,7 +213,7 @@ !(info->display_picture->flags & PIC_FLAG_PROGRESSIVE_FRAME); partialFrames.enqueue(frm); - + } } if (info->discard_fbuf) @@ -261,7 +261,7 @@ lastccptsu(0), using_null_videoout(use_null_videoout), video_codec_id(kCodec_NONE), - maxkeyframedist(-1), + maxkeyframedist(-1), ccd(new CCDecoder(this)), // Audio audioSamples(new short int[AVCODEC_MAX_AUDIO_FRAME_SIZE]), @@ -382,7 +382,7 @@ long long ts = (long long)( diff / fps ); if (av_seek_frame(ic, -1, ts, AVSEEK_FLAG_BACKWARD) < 0) { - VERBOSE(VB_IMPORTANT, LOC_ERR + VERBOSE(VB_IMPORTANT, LOC_ERR <<"av_seek_frame(ic, -1, "<0 && !ateof; skipFrames--) { - GetFrame(0); + GetFrame(0); if (decoded_video_frame) GetNVP()->DiscardVideoFrame(decoded_video_frame); } @@ -725,7 +725,7 @@ return -1; } - /* av_find_stream_info() eventually makes calls to avcodec_open() and avcodec_close() + /* av_find_stream_info() eventually makes calls to avcodec_open() and avcodec_close() so we have to use the avcodeclock */ avcodeclock.lock(); int ret = av_find_stream_info(ic); @@ -873,7 +873,7 @@ enc->rate_emu = 0; enc->error_rate = 0; - AVCodec *codec = avcodec_find_decoder(enc->codec_id); + AVCodec *codec = avcodec_find_decoder(enc->codec_id); if (!gContext->GetNumSetting("DecodeExtraAudio", 0) && codec->id != CODEC_ID_MPEG2VIDEO_XVMC && @@ -1093,7 +1093,7 @@ } } - if (enc->codec_type != CODEC_TYPE_AUDIO && + if (enc->codec_type != CODEC_TYPE_AUDIO && enc->codec_type != CODEC_TYPE_VIDEO && enc->codec_type != CODEC_TYPE_SUBTITLE) continue; @@ -1103,7 +1103,7 @@ AVCodec *codec = avcodec_find_decoder(enc->codec_id); if (!codec) { - VERBOSE(VB_IMPORTANT, LOC_ERR + + VERBOSE(VB_IMPORTANT, LOC_ERR + QString("Could not find decoder for " "codec (%1), ignoring.") .arg(codec_id_string(enc->codec_id))); @@ -1164,7 +1164,15 @@ lang_indx = lang_aud_cnt[lang]; lang_aud_cnt[lang]++; } - audioStreams.push_back(StreamInfo(i, lang, lang_indx)); + if (enc->avcodec_dual_language) + { + audioStreams.push_back(StreamInfo(i, lang, lang_indx, 0)); + audioStreams.push_back(StreamInfo(i, lang, lang_indx, 1)); + } + else + { + audioStreams.push_back(StreamInfo(i, lang, lang_indx)); + } VERBOSE(VB_AUDIO, LOC + QString( "Audio Track #%1 is A/V stream #%2 " @@ -1200,6 +1208,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 = audioStreams.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) + audioStreams.push_back(audioStreams[0]); + audioStreams[0].av_substream_index = 0; + audioStreams[1].av_substream_index = 1; + } + else + { + assert(numStreams == 2); + + if (numStreams != 2) + return false; + + // Remove extra stream + audioStreams[0].av_substream_index = -1; + audioStreams.pop_back(); + } + + // Changed + return true; +} + int get_avf_buffer(struct AVCodecContext *c, AVFrame *pic) { AvFormatDecoder *nd = (AvFormatDecoder *)(c->opaque); @@ -1339,13 +1405,13 @@ cc_code = *current++; curbytes++; - + if (buf_size - curbytes < 2) break; - + data1 = *current; data2 = *(current + 1); - + switch (cc_code) { case 0xfe: @@ -1422,10 +1488,10 @@ { cc_code = *current++; curbytes++; - + if (buf_size - curbytes < 2) break; - + data1 = *current++; data2 = *current++; curbytes += 2; @@ -1566,7 +1632,7 @@ { long long startpos = pkt->pos; - VERBOSE(VB_PLAYBACK, LOC + + VERBOSE(VB_PLAYBACK, LOC + QString("positionMap[ %1 ] == %2.") .arg(prevgoppos / keyframedist) .arg((int)startpos)); @@ -1640,7 +1706,7 @@ align_dimensions(context, awidth, aheight); GetNVP()->SetVideoParams(awidth, aheight, seqFPS, - keyframedist, aspect, + keyframedist, aspect, kScan_Detect); current_width = width; @@ -1724,12 +1790,12 @@ continue; const uint line = ((i < 18) ? i : i-18) + min_blank; - const uint field = (i<18) ? 0 : 1; + const uint field = (i<18) ? 0 : 1; const uint id2 = *buf & 0xf; switch (id2) { case VBI_TYPE_TELETEXT: - // SECAM lines 6-23 + // SECAM lines 6-23 // PAL lines 6-22 // NTSC lines 10-21 (rare) //ttd->Decode(buf+1, VBI_IVTV); @@ -1903,6 +1969,39 @@ return selectedTrack; } +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]; + } +} + /** \fn AvFormatDecoder::autoSelectAudioTrack(void) * \brief Selects the best audio track. * @@ -1915,12 +2014,12 @@ * If it can not be located we attempt to find a stream * in the same language. * - * 2) If we can not reselect the last user selected stream, + * 3) If we can not reselect the last user selected stream, * then for each preferred language from most preferred * to least preferred, we try to find a new stream based * on the algorithm below. * - * 3) If we can not select a stream in a preferred language + * 4) If we can not select a stream in a preferred language * we try to select a stream irrespective of language * based on the algorithm below. * @@ -1972,6 +2071,21 @@ int selectedTrack = (1 == numStreams) ? 0 : -1; + // Dual stream without explicit language information: choose + // the previous (sub)stream that was kept in wantedAudioStream. + if (selectedTrack < 0 && wantedAudioStream.av_substream_index != -1) + { + int substream_index = wantedAudioStream.av_substream_index; + for (uint i = 0; i < numStreams; i++) + { + if (audioStreams[i].av_substream_index == substream_index) + { + selectedTrack = i; + break; + } + } + } + if ((selectedTrack < 0) && wantedAudioStream.language>=-1 && numStreams) { VERBOSE(VB_AUDIO, LOC + "Trying to reselect audio track"); @@ -2115,7 +2229,7 @@ { uint numStreams = subtitleStreams.size(); - if ((currentSubtitleTrack >= 0) && + if ((currentSubtitleTrack >= 0) && (currentSubtitleTrack < (int)numStreams)) { return true; // subtitle already selected @@ -2176,9 +2290,9 @@ selectedSubtitleStream = subtitleStreams[currentSubtitleTrack]; if (wantedSubtitleStream.av_stream_index < 0) wantedSubtitleStream = selectedSubtitleStream; - + int lang = subtitleStreams[currentSubtitleTrack].language; - VERBOSE(VB_PLAYBACK, LOC + + VERBOSE(VB_PLAYBACK, LOC + QString("Selected subtitle track #%1 in the %2 language(%3)") .arg(currentSubtitleTrack+1) .arg(iso639_key_toName(lang)).arg(lang)); @@ -2380,6 +2494,7 @@ avcodeclock.lock(); int ctype = curstream->codec->codec_type; int audIdx = selectedAudioStream.av_stream_index; + int audSubIdx = selectedAudioStream.av_substream_index; int subIdx = selectedSubtitleStream.av_stream_index; avcodeclock.unlock(); @@ -2389,6 +2504,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) @@ -2398,16 +2522,24 @@ ret = avcodec_decode_audio( curstream->codec, audioSamples, &data_size, ptr, len); - if (curstream->codec->channels) + if (curstream->codec->channels != 0) { - currentAudioTrack = -1; - selectedAudioStream.av_stream_index = -1; - audIdx = -1; - autoSelectAudioTrack(); - audIdx = selectedAudioStream.av_stream_index; + reselectAudioTrack = true; } } + if (reselectAudioTrack) + { + QMutexLocker locker(&avcodeclock); + currentAudioTrack = -1; + selectedAudioStream.av_stream_index = -1; + audIdx = -1; + audSubIdx = -1; + autoSelectAudioTrack(); + audIdx = selectedAudioStream.av_stream_index; + audSubIdx = selectedAudioStream.av_substream_index; + } + if (firstloop && pkt->pts != (int64_t)AV_NOPTS_VALUE) lastapts = (long long)(av_q2d(curstream->time_base) * pkt->pts * 1000); @@ -2480,9 +2612,14 @@ // calc for next frame lastapts += (long long)((double)(data_size * 1000) / - (curstream->codec->channels * 2) / + (curstream->codec->channels * 2) / curstream->codec->sample_rate); + if (audSubIdx != -1) + { + filter_audio_data(audSubIdx, &audioOut, + (char *)audioSamples, data_size); + } GetNVP()->AddAudioData((char *)audioSamples, data_size, temppts); @@ -2539,21 +2676,21 @@ if (!directrendering) { AVPicture tmppicture; - + VideoFrame *xf = picframe; picframe = GetNVP()->GetNextVideoFrame(false); tmppicture.data[0] = picframe->buf; - tmppicture.data[1] = tmppicture.data[0] + + tmppicture.data[1] = tmppicture.data[0] + picframe->width * picframe->height; - tmppicture.data[2] = tmppicture.data[1] + + tmppicture.data[2] = tmppicture.data[1] + picframe->width * picframe->height / 4; tmppicture.linesize[0] = picframe->width; tmppicture.linesize[1] = picframe->width / 2; tmppicture.linesize[2] = picframe->width / 2; - img_convert(&tmppicture, PIX_FMT_YUV420P, + img_convert(&tmppicture, PIX_FMT_YUV420P, (AVPicture *)&mpa_pic, context->pix_fmt, context->width, @@ -2627,7 +2764,7 @@ ptr += len; len = 0; - if (gotSubtitles) + if (gotSubtitles) { subtitle.start_display_time += pts; subtitle.end_display_time += pts; @@ -2707,7 +2844,7 @@ { assert(curstream); assert(curstream->codec); - codec_ctx = curstream->codec; + codec_ctx = curstream->codec; bool do_ac3_passthru = (allow_ac3_passthru && !transcoding && !disable_passthru && (codec_ctx->codec_id == CODEC_ID_AC3)); @@ -2843,7 +2980,7 @@ { m_parent->AddTextData((char*)buf, len, timecode, type); } - + static int DTS_SAMPLEFREQS[16] = { 0, 8000, 16000, 32000, 64000, 128000, 11025, 22050, Index: libs/libmythtv/avformatdecoder.h =================================================================== --- libs/libmythtv/avformatdecoder.h (revision 11213) +++ libs/libmythtv/avformatdecoder.h (working copy) @@ -23,11 +23,14 @@ class StreamInfo { public: - StreamInfo() : av_stream_index(-1), language(-2), language_index(0) {} + StreamInfo() : av_stream_index(-1), av_substream_index(-1), language(-2), language_index(0) {} StreamInfo(int a, int b, uint c) - : av_stream_index(a), language(b), language_index(c) {} + : av_stream_index(a), av_substream_index(-1), language(b), language_index(c) {} + StreamInfo(int a, int b, uint c, int d) + : av_stream_index(a), av_substream_index(d), language(b), language_index(c) {} public: int av_stream_index; + int av_substream_index; // -1 = all, otherwise a channel number int language; ///< ISO639 canonical language key uint language_index; }; @@ -127,17 +130,18 @@ virtual void SetDisablePassThrough(bool disable); virtual void incCurrentAudioTrack(); virtual void decCurrentAudioTrack(); - virtual bool setCurrentAudioTrack(int trackNo); + virtual bool setCurrentAudioTrack(int trackNo); virtual QStringList listAudioTracks() const; void AddTextData(unsigned char *buf, int len, long long timecode, char type); virtual void incCurrentSubtitleTrack(); virtual void decCurrentSubtitleTrack(); - virtual bool setCurrentSubtitleTrack(int trackNo); + virtual bool setCurrentSubtitleTrack(int trackNo); virtual QStringList listSubtitleTracks() const; int ScanStreams(bool novideo); + bool FixAudioStreamSubIndexes(bool wasDual, bool isDual); virtual bool DoRewind(long long desiredFrame, bool doflush = true); virtual bool DoFastForward(long long desiredFrame, bool doflush = true); Index: libs/libmythtv/mpegrecorder.cpp =================================================================== --- libs/libmythtv/mpegrecorder.cpp (revision 11213) +++ libs/libmythtv/mpegrecorder.cpp (working copy) @@ -46,6 +46,7 @@ "DVD-Special 2", 0 }; const char* MpegRecorder::aspectRatio[] = { "Square", "4:3", "16:9", "2.21:1", 0 }; +const char* MpegRecorder::languages[] = { "Language I", "Language II", "Dual" }; #define BUILDBUFFERMAX (1024 * 1024) @@ -75,6 +76,7 @@ audbitratel1 = 14; audbitratel2 = 14; audvolume = 80; + language = 0; deviceIsMpegFile = false; bufferSize = 4096; @@ -175,6 +177,22 @@ if (!found) cerr << "MPEG2 stream type: " << value << " is invalid\n"; } + 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; @@ -235,6 +253,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()); @@ -357,6 +377,45 @@ if (ivtvcodec.framerate) keyframedist = 12; + 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) @@ -193,6 +193,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: @@ -222,6 +242,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/libmythtv/mpegrecorder.h =================================================================== --- libs/libmythtv/mpegrecorder.h (revision 11213) +++ libs/libmythtv/mpegrecorder.h (working copy) @@ -74,6 +74,7 @@ int bitrate, maxbitrate, streamtype, aspectratio; int audtype, audsamplerate, audbitratel1, audbitratel2; int audvolume; + int language; int chanfd; int readfd; @@ -89,6 +90,7 @@ static const int audRateL2[]; static const char* streamType[]; static const char* aspectRatio[]; + static const char* languages[]; unsigned int leftovers; long long lastpackheaderpos; Index: libs/libavcodec/mpegaudiodec.c =================================================================== --- libs/libavcodec/mpegaudiodec.c (revision 11213) +++ libs/libavcodec/mpegaudiodec.c (working copy) @@ -1229,6 +1229,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) @@ -855,6 +855,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)