MythTV  0.26-pre
audiogeneralsettings.cpp
Go to the documentation of this file.
00001 
00002 // -*- Mode: c++ -*-
00003 
00004 // Standard UNIX C headers
00005 #include <unistd.h>
00006 #include <fcntl.h>
00007 #include <sys/types.h>
00008 #include <sys/stat.h>
00009 
00010 // Qt headers
00011 #include <QCoreApplication>
00012 #include <QEvent>
00013 #include <QDir>
00014 
00015 // MythTV headers
00016 #include "mythconfig.h"
00017 #include "mythcorecontext.h"
00018 #include "mythdbcon.h"
00019 #include "audiooutpututil.h"
00020 #include "audiogeneralsettings.h"
00021 #include "mythdialogbox.h"
00022 #include "mythlogging.h"
00023 
00024 extern "C" {
00025 #include "libavformat/avformat.h"
00026 }
00027 
00028 
00029 class TriggeredItem : public TriggeredConfigurationGroup
00030 {
00031   public:
00032     TriggeredItem(Setting *checkbox, ConfigurationGroup *group) :
00033         TriggeredConfigurationGroup(false, false, false, false)
00034     {
00035         setTrigger(checkbox);
00036 
00037         addTarget("1", group);
00038         addTarget("0", new VerticalConfigurationGroup(true));
00039     }
00040     TriggeredItem(Setting *checkbox, Setting *setting) :
00041         TriggeredConfigurationGroup(false, false, false, false)
00042     {
00043         setTrigger(checkbox);
00044 
00045         addTarget("1", setting);
00046         addTarget("0", new VerticalConfigurationGroup(false, false));
00047     }
00048 };
00049 
00050 AudioDeviceComboBox::AudioDeviceComboBox(AudioConfigSettings *parent) :
00051     HostComboBox("AudioOutputDevice", true), m_parent(parent)
00052 {
00053     setLabel(QObject::tr("Audio output device"));
00054 #ifdef USING_ALSA
00055     QString dflt = "ALSA:default";
00056 #elif USING_PULSEOUTPUT
00057     QString dflt = "PulseAudio:default";
00058 #elif CONFIG_DARWIN
00059     QString dflt = "CoreAudio:";
00060 #elif USING_MINGW
00061     QString dflt = "Windows:";
00062 #else
00063     QString dflt = "NULL";
00064 #endif
00065     QString current = gCoreContext->GetSetting(QString("AudioOutputDevice"),
00066                                                dflt);
00067     addSelection(current, current, true);
00068 
00069     connect(this, SIGNAL(valueChanged(const QString&)),
00070             this, SLOT(AudioDescriptionHelp(const QString&)));
00071 }
00072 
00073 void AudioDeviceComboBox::AudioRescan()
00074 {
00075     AudioOutput::ADCVect &vect = m_parent->AudioDeviceVect();
00076     AudioOutput::ADCVect::const_iterator it;
00077 
00078     if (vect.empty())
00079         return;
00080 
00081     QString value = getValue();
00082     clearSelections();
00083     resetMaxCount(vect.size());
00084 
00085     bool found = false;
00086     for (it = vect.begin(); it != vect.end(); ++it)
00087         addSelection(it->name, it->name,
00088                      value == it->name ? (found = true) : false);
00089     if (!found)
00090     {
00091         resetMaxCount(vect.size()+1);
00092         addSelection(value, value, true);
00093     }
00094         // For some reason, it adds an empty entry, remove it
00095     removeSelection(QString::null);
00096 }
00097 
00098 void AudioDeviceComboBox::AudioDescriptionHelp(const QString &device)
00099 {
00100     QString desc = m_parent->AudioDeviceMap().value(device).desc;
00101     setHelpText(desc);
00102 }
00103 
00104 AudioConfigSettings::AudioConfigSettings(ConfigurationWizard *parent) :
00105     VerticalConfigurationGroup(false, true, false, false),
00106     m_OutputDevice(NULL),   m_MaxAudioChannels(NULL),
00107     m_AudioUpmix(NULL),     m_AudioUpmixType(NULL),
00108     m_AC3PassThrough(NULL), m_DTSPassThrough(NULL),
00109     m_EAC3PassThrough(NULL),m_TrueHDPassThrough(NULL), m_DTSHDPassThrough(NULL),
00110     m_parent(parent),       m_lastAudioDevice("")
00111 {
00112     setLabel(QObject::tr("Audio System"));
00113     setUseLabel(false);
00114 
00115     ConfigurationGroup *devicegroup = new HorizontalConfigurationGroup(false,
00116                                                                        false);
00117     devicegroup->addChild((m_OutputDevice = new AudioDeviceComboBox(this)));
00118         // Rescan button
00119     TransButtonSetting *rescan = new TransButtonSetting("rescan");
00120     rescan->setLabel(QObject::tr("Rescan"));
00121     rescan->setHelpText(QObject::tr("Rescan for available audio devices. "
00122                                     "Current entry will be checked and "
00123                                     "capability entries populated."));
00124     devicegroup->addChild(rescan);
00125     connect(rescan, SIGNAL(pressed()), this, SLOT(AudioRescan()));
00126     addChild(devicegroup);
00127 
00128     QString name = m_OutputDevice->getValue();
00129     AudioOutput::AudioDeviceConfig *adc =
00130         AudioOutput::GetAudioDeviceConfig(name, name, true);
00131     if (adc->settings.IsInvalid())
00132     {
00133         LOG(VB_GENERAL, LOG_ERR,
00134             QString("Audio device %1 isn't usable Check audio configuration")
00135                 .arg(name));
00136     }
00137     audiodevs.insert(name, *adc);
00138     devices.append(*adc);
00139 
00140     delete adc;
00141 
00142     ConfigurationGroup *maingroup = new VerticalConfigurationGroup(false,
00143                                                                    false);
00144     addChild(maingroup);
00145 
00146     m_triggerDigital = new TransCheckBoxSetting();
00147     m_AC3PassThrough = AC3PassThrough();
00148     m_DTSPassThrough = DTSPassThrough();
00149     m_EAC3PassThrough = EAC3PassThrough();
00150     m_TrueHDPassThrough = TrueHDPassThrough();
00151     m_DTSHDPassThrough = DTSHDPassThrough();
00152 
00153     m_cgsettings = new HorizontalConfigurationGroup();
00154     m_cgsettings->setLabel(QObject::tr("Digital Audio Capabilities"));
00155     m_cgsettings->addChild(m_AC3PassThrough);
00156     m_cgsettings->addChild(m_DTSPassThrough);
00157     m_cgsettings->addChild(m_EAC3PassThrough);
00158     m_cgsettings->addChild(m_TrueHDPassThrough);
00159     m_cgsettings->addChild(m_DTSHDPassThrough);
00160 
00161     TriggeredItem *sub1 = new TriggeredItem(m_triggerDigital, m_cgsettings);
00162 
00163     maingroup->addChild(sub1);
00164 
00165     maingroup->addChild((m_MaxAudioChannels = MaxAudioChannels()));
00166     maingroup->addChild((m_AudioUpmix = AudioUpmix()));
00167     maingroup->addChild((m_AudioUpmixType = AudioUpmixType()));
00168 
00169     TransButtonSetting *test = new TransButtonSetting("test");
00170     test->setLabel(QObject::tr("Test"));
00171     test->setHelpText(QObject::tr("Will play a test pattern on all configured "
00172                                   "speakers"));
00173     connect(test, SIGNAL(pressed()), this, SLOT(StartAudioTest()));
00174     addChild(test);
00175 
00176     TransButtonSetting *advanced = new TransButtonSetting("advanced");
00177     advanced->setLabel(QObject::tr("Advanced Audio Settings"));
00178     advanced->setHelpText(QObject::tr("Enable extra audio settings. Under most "
00179                                   "usage all options should be left alone"));
00180     connect(advanced, SIGNAL(pressed()), this, SLOT(AudioAdvanced()));
00181     addChild(advanced);
00182 
00183         // Set slots
00184     connect(m_MaxAudioChannels, SIGNAL(valueChanged(const QString&)),
00185             this, SLOT(UpdateVisibility(const QString&)));
00186     connect(m_OutputDevice, SIGNAL(valueChanged(const QString&)),
00187             this, SLOT(UpdateCapabilities(const QString&)));
00188     connect(m_AC3PassThrough, SIGNAL(valueChanged(const QString&)),
00189             this, SLOT(UpdateCapabilitiesAC3()));
00190 
00191     m_maxspeakers = gCoreContext->GetNumSetting("MaxChannels", 2);
00192     AudioRescan();
00193 }
00194 
00195 void AudioConfigSettings::AudioRescan()
00196 {
00197     if (!slotlock.tryLock())
00198         return;
00199 
00200     AudioOutput::ADCVect* list = AudioOutput::GetOutputList();
00201     AudioOutput::ADCVect::const_iterator it;
00202 
00203     audiodevs.clear();
00204     for (it = list->begin(); it != list->end(); ++it)
00205         audiodevs.insert(it->name, *it);
00206 
00207     devices = *list;
00208     delete list;
00209 
00210     QString name = m_OutputDevice->getValue();
00211     if (!audiodevs.contains(name))
00212     {
00213             // Scan for possible custom entry that isn't in the list
00214         AudioOutput::AudioDeviceConfig *adc =
00215             AudioOutput::GetAudioDeviceConfig(name, name, true);
00216         if (adc->settings.IsInvalid())
00217         {
00218             QString msg = name + QObject::tr(" is invalid or not useable.");
00219             MythPopupBox::showOkPopup(
00220                 GetMythMainWindow(), QObject::tr("Warning"), msg);
00221             LOG(VB_GENERAL, LOG_ERR, QString("Audio device %1 isn't usable")
00222                     .arg(name));
00223         }
00224         audiodevs.insert(name, *adc);
00225         devices.append(*adc);
00226         delete adc;
00227     }
00228     m_OutputDevice->AudioRescan();
00229     if (!CheckPassthrough())
00230     {
00231         QString msg =QObject::tr("Passthrough device is invalid or not useable."
00232                                  " Check configuration in Advanced Settings:") +
00233             gCoreContext->GetSetting("PassThruOutputDevice");
00234         MythPopupBox::showOkPopup(
00235             GetMythMainWindow(), QObject::tr("Warning"), msg);
00236         LOG(VB_GENERAL, LOG_ERR, QString("Audio device %1 isn't usable")
00237                 .arg(name));
00238     }
00239     slotlock.unlock();
00240     UpdateCapabilities(QString::null);
00241 }
00242 
00243 void AudioConfigSettings::UpdateVisibility(const QString &device)
00244 {
00245     if (!m_MaxAudioChannels && !m_AudioUpmix && !m_AudioUpmixType)
00246         return;
00247 
00248     int cur_speakers = m_MaxAudioChannels->getValue().toInt();
00249     m_AudioUpmix->setEnabled(cur_speakers > 2);
00250     m_AudioUpmixType->setEnabled(cur_speakers > 2);
00251 }
00252 
00253 AudioOutputSettings AudioConfigSettings::UpdateCapabilities(
00254     const QString &device, bool restore, bool AC3)
00255 {
00256     int max_speakers = 8;
00257     int realmax_speakers = 8;
00258 
00259     bool invalid = false;
00260 
00261     if (device.length() > 0)
00262     {
00263         restore = device != m_lastAudioDevice;
00264         m_lastAudioDevice = device;
00265     }
00266 
00267     AudioOutputSettings settings, settingsdigital;
00268 
00269         // Test if everything is set yet
00270     if (!m_OutputDevice    || !m_MaxAudioChannels   ||
00271         !m_AC3PassThrough  || !m_DTSPassThrough     ||
00272         !m_EAC3PassThrough || !m_TrueHDPassThrough  || !m_DTSHDPassThrough)
00273         return settings;
00274 
00275     if (!slotlock.tryLock()) // Doing a rescan of channels
00276         return settings;
00277 
00278     bool bAC3 = true;
00279     bool bDTS = true;
00280     bool bLPCM = true;
00281     bool bEAC3 = true;
00282     bool bTRUEHD = true;
00283     bool bDTSHD = true;
00284 
00285     QString out = m_OutputDevice->getValue();
00286     if (!audiodevs.contains(out))
00287     {
00288         LOG(VB_AUDIO, LOG_ERR, QString("Update not found (%1)").arg(out));
00289         invalid = true;
00290     }
00291     else
00292     {
00293         bool bForceDigital =
00294             gCoreContext->GetNumSetting("PassThruDeviceOverride", false);
00295 
00296         settings = audiodevs.value(out).settings;
00297         settingsdigital = bForceDigital ?
00298             audiodevs.value(gCoreContext->GetSetting("PassThruOutputDevice"))
00299             .settings : settings;
00300 
00301         realmax_speakers = max_speakers = settings.BestSupportedChannels();
00302 
00303         bAC3  = settingsdigital.canFeature(FEATURE_AC3) &&
00304             m_AC3PassThrough->boolValue();
00305         bDTS  = settingsdigital.canFeature(FEATURE_DTS)  &&
00306             m_DTSPassThrough->boolValue();
00307         bLPCM = settings.canFeature(FEATURE_LPCM) &&
00308             !gCoreContext->GetNumSetting("StereoPCM", false);
00309         bEAC3 = settingsdigital.canFeature(FEATURE_EAC3) &&
00310             !gCoreContext->GetNumSetting("Audio48kOverride", false);
00311         bTRUEHD = settingsdigital.canFeature(FEATURE_TRUEHD) &&
00312             !gCoreContext->GetNumSetting("Audio48kOverride", false) &&
00313             gCoreContext->GetNumSetting("HBRPassthru", true);
00314         bDTSHD = settingsdigital.canFeature(FEATURE_DTSHD) &&
00315             !gCoreContext->GetNumSetting("Audio48kOverride", false);
00316 
00317         if (max_speakers > 2 && !bLPCM)
00318             max_speakers = 2;
00319         if (max_speakers == 2 && bAC3)
00320         {
00321             max_speakers = 6;
00322             if (AC3)
00323             {
00324                 restore = true;
00325             }
00326         }
00327     }
00328 
00329     m_triggerDigital->setValue(invalid || settingsdigital.canFeature(
00330                                    FEATURE_AC3 | FEATURE_DTS | FEATURE_EAC3 |
00331                                    FEATURE_TRUEHD | FEATURE_DTSHD));
00332     m_EAC3PassThrough->setEnabled(bEAC3);
00333     m_TrueHDPassThrough->setEnabled(bTRUEHD);
00334     m_DTSHDPassThrough->setEnabled(bDTSHD);
00335 
00336     int cur_speakers = m_MaxAudioChannels->getValue().toInt();
00337     if (cur_speakers > m_maxspeakers)
00338     {
00339         m_maxspeakers = m_MaxAudioChannels->getValue().toInt();
00340     }
00341     if (restore)
00342     {
00343         cur_speakers = m_maxspeakers;
00344     }
00345 
00346     if (cur_speakers > max_speakers)
00347     {
00348         LOG(VB_AUDIO, LOG_INFO, QString("Reset device %1").arg(out));
00349         cur_speakers = max_speakers;
00350     }
00351 
00352         // Remove everything and re-add available channels
00353     m_MaxAudioChannels->clearSelections();
00354     m_MaxAudioChannels->resetMaxCount(3);
00355     for (int i = 1; i <= max_speakers; i++)
00356     {
00357         if (invalid || settings.IsSupportedChannels(i) ||
00358             settingsdigital.IsSupportedChannels(i))
00359         {
00360             QString txt;
00361 
00362             switch (i)
00363             {
00364                 case 2:
00365                     txt = QObject::tr("Stereo");
00366                     break;
00367                 case 6:
00368                     txt = QObject::tr("5.1");
00369                     break;
00370                 case 8:
00371                     txt = QObject::tr("7.1");
00372                     break;
00373                 default:
00374                     continue;
00375             }
00376             m_MaxAudioChannels->addSelection(txt, QString::number(i),
00377                                              i == cur_speakers);
00378         }
00379     }
00380         // Return values is used by audio test
00381         // where we mainly are interested by the number of channels
00382         // if we support AC3 and/or LPCM
00383     settings.SetBestSupportedChannels(cur_speakers);
00384     settings.setFeature(bAC3, FEATURE_AC3);
00385     settings.setFeature(bLPCM && realmax_speakers > 2, FEATURE_LPCM);
00386 
00387     slotlock.unlock();
00388     return settings;
00389 }
00390 
00391 AudioOutputSettings AudioConfigSettings::UpdateCapabilitiesAC3(void)
00392 {
00393     return UpdateCapabilities(QString::null, false, true);
00394 }
00395 
00396 void AudioConfigSettings::AudioAdvanced()
00397 {
00398     QString out  = m_OutputDevice->getValue();
00399     bool invalid = false;
00400     AudioOutputSettings settings;
00401 
00402     if (!audiodevs.contains(out))
00403     {
00404         invalid = true;
00405     }
00406     else
00407     {
00408         settings = audiodevs.value(out).settings;
00409     }
00410 
00411     bool LPCM1 = settings.canFeature(FEATURE_LPCM) &&
00412         gCoreContext->GetNumSetting("StereoPCM", false);
00413 
00414     AudioAdvancedSettingsGroup audiosettings(invalid ||
00415                                              (settings.canPassthrough() >= 0));
00416 
00417     if (audiosettings.exec() == kDialogCodeAccepted)
00418     {
00419         // Rescan audio list to check of override digital device
00420         AudioRescan();
00421         bool LPCM2 = settings.canFeature(FEATURE_LPCM) &&
00422             gCoreContext->GetNumSetting("StereoPCM", false);
00423             // restore speakers configure only of StereoPCM has changed and
00424             // we have LPCM capabilities
00425         UpdateCapabilities(QString::null, LPCM1 != LPCM2);
00426     }
00427 }
00428 
00429 HostComboBox *AudioConfigSettings::MaxAudioChannels()
00430 {
00431     QString name = "MaxChannels";
00432     HostComboBox *gc = new HostComboBox(name, false);
00433     gc->setLabel(QObject::tr("Speaker configuration"));
00434     gc->addSelection(QObject::tr("Stereo"), "2", true); // default
00435     gc->setHelpText(QObject::tr("Select the maximum number of audio "
00436                                 "channels supported by your receiver "
00437                                 "and speakers."));
00438     return gc;
00439 }
00440 
00441 HostCheckBox *AudioConfigSettings::AudioUpmix()
00442 {
00443     HostCheckBox *gc = new HostCheckBox("AudioDefaultUpmix");
00444     gc->setLabel(QObject::tr("Upconvert stereo to 5.1 surround"));
00445     gc->setValue(true);
00446     gc->setHelpText(QObject::tr("If enabled, MythTV will upconvert stereo "
00447                     "to 5.1 audio. You can enable or disable "
00448                     "the upconversion during playback at any time."));
00449     return gc;
00450 }
00451 
00452 HostComboBox *AudioConfigSettings::AudioUpmixType()
00453 {
00454     HostComboBox *gc = new HostComboBox("AudioUpmixType",false);
00455     gc->setLabel(QObject::tr("Upmix Quality"));
00456     gc->addSelection(QObject::tr("Good"), "1");
00457     gc->addSelection(QObject::tr("Best"), "2", true);  // default
00458     gc->setHelpText(QObject::tr("Set the audio surround-upconversion quality."));
00459     return gc;
00460 }
00461 
00462 HostCheckBox *AudioConfigSettings::AC3PassThrough()
00463 {
00464     HostCheckBox *gc = new HostCheckBox("AC3PassThru");
00465     gc->setLabel(QObject::tr("Dolby Digital"));
00466     gc->setValue(false);
00467     gc->setHelpText(QObject::tr("Enable if your amplifier or sound decoder "
00468                     "supports AC-3/Dolby Digital. You must use a digital "
00469                     "connection. Uncheck if using an analog connection."));
00470     return gc;
00471 }
00472 
00473 HostCheckBox *AudioConfigSettings::DTSPassThrough()
00474 {
00475     HostCheckBox *gc = new HostCheckBox("DTSPassThru");
00476     gc->setLabel(QObject::tr("DTS"));
00477     gc->setValue(false);
00478     gc->setHelpText(QObject::tr("Enable if your amplifier or sound decoder "
00479                     "supports DTS. You must use a digital connection. Uncheck "
00480                     "if using an analog connection"));
00481     return gc;
00482 }
00483 
00484 HostCheckBox *AudioConfigSettings::EAC3PassThrough()
00485 {
00486     HostCheckBox *gc = new HostCheckBox("EAC3PassThru");
00487     gc->setLabel(QObject::tr("E-AC-3"));
00488     gc->setValue(false);
00489     gc->setHelpText(QObject::tr("Enable if your amplifier or sound decoder "
00490                     "supports E-AC-3 (DD+). You must use a HDMI connection."));
00491     return gc;
00492 }
00493 
00494 HostCheckBox *AudioConfigSettings::TrueHDPassThrough()
00495 {
00496     HostCheckBox *gc = new HostCheckBox("TrueHDPassThru");
00497     gc->setLabel(QObject::tr("TrueHD"));
00498     gc->setValue(false);
00499     gc->setHelpText(QObject::tr("Enable if your amplifier or sound decoder "
00500                     "supports Dolby TrueHD. You must use a HDMI connection."));
00501     return gc;
00502 }
00503 
00504 HostCheckBox *AudioConfigSettings::DTSHDPassThrough()
00505 {
00506     HostCheckBox *gc = new HostCheckBox("DTSHDPassThru");
00507     gc->setLabel(QObject::tr("DTS-HD"));
00508     gc->setValue(false);
00509     gc->setHelpText(QObject::tr("Enable if your amplifier or sound decoder "
00510                     "supports DTS-HD. You must use a HDMI connection."));
00511     return gc;
00512 }
00513 
00514 bool AudioConfigSettings::CheckPassthrough()
00515 {
00516     bool ok = true;
00517 
00518     if (gCoreContext->GetNumSetting("PassThruDeviceOverride", false))
00519     {
00520         QString name = gCoreContext->GetSetting("PassThruOutputDevice");
00521         AudioOutput::AudioDeviceConfig *adc =
00522             AudioOutput::GetAudioDeviceConfig(name, name, true);
00523         if (adc->settings.IsInvalid())
00524         {
00525             LOG(VB_GENERAL, LOG_ERR,
00526                 QString("Passthru device %1 isn't usable "
00527                         "Check audio configuration").arg(name));
00528             ok = false;
00529         }
00530         // add it to list of known devices
00531         audiodevs.insert(name, *adc);
00532         devices.append(*adc);
00533         delete adc;
00534     }
00535     return ok;
00536 }
00537 
00538 void AudioConfigSettings::StartAudioTest()
00539 {
00540     AudioOutputSettings settings = UpdateCapabilities(QString::null,
00541                                                       false, false);
00542     QString out = m_OutputDevice->getValue();
00543     QString passthrough =
00544         gCoreContext->GetNumSetting("PassThruDeviceOverride", false) ?
00545         gCoreContext->GetSetting("PassThruOutputDevice") : QString::null;
00546     int channels = m_MaxAudioChannels->getValue().toInt();
00547 
00548     AudioTestGroup audiotest(out, passthrough, channels, settings);
00549 
00550     audiotest.exec();
00551 }
00552 
00553 AudioTestThread::AudioTestThread(QObject *parent,
00554                                  QString main, QString passthrough,
00555                                  int channels,
00556                                  AudioOutputSettings settings,
00557                                  bool hd) :
00558     MThread("AudioTest"),
00559     m_parent(parent), m_channels(channels), m_device(main),
00560     m_passthrough(passthrough), m_interrupted(false), m_channel(-1), m_hd(hd)
00561 {
00562     m_format = hd ? settings.BestSupportedFormat() : FORMAT_S16;
00563     m_samplerate = hd ? settings.BestSupportedRate() : 48000;
00564 
00565     m_audioOutput = AudioOutput::OpenAudio(m_device, m_passthrough,
00566                                            m_format, m_channels,
00567                                            0, m_samplerate,
00568                                            AUDIOOUTPUT_VIDEO,
00569                                            true, false, 0, &settings);
00570     if (result().isEmpty())
00571     {
00572         m_audioOutput->Pause(true);
00573     }
00574 }
00575 
00576 QEvent::Type ChannelChangedEvent::kEventType =
00577     (QEvent::Type) QEvent::registerEventType();
00578 
00579 AudioTestThread::~AudioTestThread()
00580 {
00581     cancel();
00582     wait();
00583     if (m_audioOutput)
00584         delete m_audioOutput;
00585 }
00586 
00587 void AudioTestThread::cancel()
00588 {
00589     m_interrupted = true;
00590 }
00591 
00592 QString AudioTestThread::result()
00593 {
00594     QString errMsg;
00595     if (!m_audioOutput)
00596         errMsg = QObject::tr("Unable to create AudioOutput.");
00597     else
00598         errMsg = m_audioOutput->GetError();
00599     return errMsg;
00600 }
00601 
00602 void AudioTestThread::setChannel(int channel)
00603 {
00604     m_channel = channel;
00605 }
00606 
00607 void AudioTestThread::run()
00608 {
00609     RunProlog();
00610     m_interrupted = false;
00611     int smptelayout[7][8] = { 
00612         { 0, 1, 1 },                    //stereo
00613         { },                            //not used
00614         { },                            //not used
00615         { 0, 2, 1, 4, 3 },              //5.0
00616         { 0, 2, 1, 5, 4, 3 },           //5.1
00617         { 0, 2, 1, 6, 4, 5, 3 },        //6.1
00618         { 0, 2, 1, 7, 5, 4, 6, 3 },     //7.1
00619     };
00620 
00621     if (m_audioOutput)
00622     {
00623         char *frames_in = new char[m_channels * 1024 * sizeof(int32_t) + 15];
00624         char *frames = (char *)(((long)frames_in + 15) & ~0xf);
00625 
00626         m_audioOutput->Pause(false);
00627 
00628         int begin = 0;
00629         int end = m_channels;
00630         if (m_channel >= 0)
00631         {
00632             begin = m_channel;
00633             end = m_channel + 1;
00634         }
00635         while (!m_interrupted)
00636         {
00637             for (int i = begin; i < end && !m_interrupted; i++)
00638             {
00639                 int current = smptelayout[m_channels - 2][i];
00640 
00641                 if (m_parent)
00642                 {
00643                     QString channel;
00644 
00645                     switch(current)
00646                     {
00647                         case 0:
00648                             channel = "frontleft";
00649                             break;
00650                         case 1:
00651                             channel = "frontright";
00652                             break;
00653                         case 2:
00654                             channel = "center";
00655                             break;
00656                         case 3:
00657                             channel = "lfe";
00658                             break;
00659                         case 4:
00660                             if (m_channels == 6)
00661                                 channel = "surroundleft";
00662                             else if (m_channels == 7)
00663                                 channel = "rearright";
00664                             else
00665                                 channel = "rearleft";
00666                             break;
00667                         case 5:
00668                             if (m_channels == 6)
00669                                 channel = "surroundright";
00670                             else if (m_channels == 7)
00671                                 channel = "surroundleft";
00672                             else
00673                                 channel = "rearright";
00674                             break;
00675                         case 6:
00676                             if (m_channels == 7)
00677                                 channel = "surroundright";
00678                             else
00679                                 channel = "surroundleft";
00680                             break;
00681                         case 7:
00682                             channel = "surroundright";
00683                             break;
00684                     }
00685                     QCoreApplication::postEvent(
00686                         m_parent, new ChannelChangedEvent(channel,
00687                                                           m_channel < 0));
00688                     LOG(VB_AUDIO, LOG_INFO, QString("AudioTest: %1 (%2->%3)")
00689                             .arg(channel).arg(i).arg(current));
00690                 }
00691 
00692                     // play sample sound for about 3s
00693                 int top = m_samplerate / 1000 * 3;
00694                 for (int j = 0; j < top && !m_interrupted; j++)
00695                 {
00696                     AudioOutputUtil::GeneratePinkFrames(frames, m_channels,
00697                                                         current, 1000,
00698                                                         m_hd ? 32 : 16);
00699                     if (!m_audioOutput->AddFrames(frames, 1000 , -1))
00700                     {
00701                         LOG(VB_AUDIO, LOG_ERR, "AddData() Audio buffer "
00702                                                "overflow, audio data lost!");
00703                     }
00704                     usleep(m_audioOutput->LengthLastData() * 1000);
00705                 }
00706                 m_audioOutput->Drain();
00707                 m_audioOutput->Pause(true);
00708                 usleep(500000); // .5s pause
00709                 m_audioOutput->Pause(false);
00710             }
00711             if (m_channel >= 0)
00712                 break;
00713         }
00714         m_audioOutput->Pause(true);
00715 
00716         delete[] frames_in;
00717     }
00718     RunEpilog();
00719 }
00720 
00721 AudioTest::AudioTest(QString main, QString passthrough,
00722                      int channels, AudioOutputSettings settings) 
00723     : VerticalConfigurationGroup(false, true, false, false),
00724       m_frontleft(NULL), m_frontright(NULL), m_center(NULL),
00725       m_surroundleft(NULL), m_surroundright(NULL),
00726       m_rearleft(NULL), m_rearright(NULL), m_lfe(NULL),
00727       m_main(main), m_passthrough(passthrough), m_settings(settings),
00728       m_quality(false)
00729 {
00730     m_channels = gCoreContext->GetNumSetting("TestingChannels", channels);
00731     setLabel(QObject::tr("Audio Configuration Testing"));
00732 
00733     m_at = new AudioTestThread(this, m_main, m_passthrough, m_channels,
00734                                m_settings, m_quality);
00735     if (!m_at->result().isEmpty())
00736     {
00737         QString msg = main + QObject::tr(" is invalid or not "
00738                                          "useable.");
00739         MythPopupBox::showOkPopup(
00740             GetMythMainWindow(), QObject::tr("Warning"), msg);
00741         return;
00742     }
00743 
00744     m_button = new TransButtonSetting("start");
00745     m_button->setLabel(QObject::tr("Test All"));
00746     m_button->setHelpText(QObject::tr("Start all channels test"));
00747     connect(m_button, SIGNAL(pressed(QString)), this, SLOT(toggle(QString)));
00748 
00749     ConfigurationGroup *frontgroup =
00750         new HorizontalConfigurationGroup(false,
00751                                          false);
00752     ConfigurationGroup *middlegroup =
00753         new HorizontalConfigurationGroup(false,
00754                                          false);
00755     ConfigurationGroup *reargroup =
00756         new HorizontalConfigurationGroup(false,
00757                                          false);
00758     m_frontleft = new TransButtonSetting("0");
00759     m_frontleft->setLabel(QObject::tr("Front Left"));
00760     connect(m_frontleft,
00761             SIGNAL(pressed(QString)), this, SLOT(toggle(QString)));
00762     m_frontright = new TransButtonSetting(m_channels == 2 ? "1" : "2");
00763     m_frontright->setLabel(QObject::tr("Front Right"));
00764     connect(m_frontright,
00765             SIGNAL(pressed(QString)), this, SLOT(toggle(QString)));
00766     frontgroup->addChild(m_frontleft);
00767 
00768     switch(m_channels)
00769     {
00770         case 8:
00771             m_rearleft = new TransButtonSetting("5");
00772             m_rearleft->setLabel(QObject::tr("Rear Left"));
00773             connect(m_rearleft,
00774                     SIGNAL(pressed(QString)), this, SLOT(toggle(QString)));
00775             reargroup->addChild(m_rearleft);
00776 
00777         case 7:
00778             m_rearright = new TransButtonSetting("4");
00779             m_rearright->setLabel(QObject::tr(m_channels == 8 ?
00780                                               "Rear Right" : "Rear Center"));
00781             connect(m_rearright,
00782                     SIGNAL(pressed(QString)), this, SLOT(toggle(QString)));
00783 
00784             reargroup->addChild(m_rearright);
00785 
00786         case 6:
00787             m_lfe = new TransButtonSetting(m_channels == 6 ? "5" :
00788                                            m_channels == 7 ? "6" : "7");
00789             m_lfe->setLabel(QObject::tr("LFE"));
00790             connect(m_lfe,
00791                     SIGNAL(pressed(QString)), this, SLOT(toggle(QString)));
00792 
00793         case 5:
00794             m_surroundleft = new TransButtonSetting(m_channels == 6 ? "4" :
00795                                                     m_channels == 7 ? "5" : "6");
00796             m_surroundleft->setLabel(QObject::tr("Surround Left"));
00797             connect(m_surroundleft,
00798                     SIGNAL(pressed(QString)), this, SLOT(toggle(QString)));
00799             m_surroundright = new TransButtonSetting("3");
00800             m_surroundright->setLabel(QObject::tr("Surround Right"));
00801             connect(m_surroundright,
00802                     SIGNAL(pressed(QString)), this, SLOT(toggle(QString)));
00803             m_center = new TransButtonSetting("1");
00804             m_center->setLabel(QObject::tr("Center"));
00805             connect(m_center,
00806                     SIGNAL(pressed(QString)), this, SLOT(toggle(QString)));
00807             frontgroup->addChild(m_center);
00808             middlegroup->addChild(m_surroundleft);
00809             if (m_lfe)
00810                 middlegroup->addChild(m_lfe);
00811             middlegroup->addChild(m_surroundright);
00812 
00813         case 2:
00814             break;
00815     }
00816     frontgroup->addChild(m_frontright);
00817     addChild(frontgroup);
00818     addChild(middlegroup);
00819     addChild(reargroup);
00820     addChild(m_button);
00821 
00822     m_hd = new TransCheckBoxSetting();
00823     m_hd->setLabel(QObject::tr("Use Highest Quality Mode"));
00824     m_hd->setHelpText(QObject::tr("Use the highest audio quality settings "
00825                                   "supported by your audio card. This will be "
00826                                   "a good place to start troubleshooting "
00827                                   "potential errors"));
00828     addChild(m_hd);
00829     connect(m_hd, SIGNAL(valueChanged(QString)), this, SLOT(togglequality()));
00830 }
00831 
00832 AudioTest::~AudioTest()
00833 {
00834     m_at->cancel();
00835     m_at->wait();
00836     delete m_at;
00837 }
00838 
00839 void AudioTest::toggle(QString str)
00840 {
00841     if (str == "start")
00842     {
00843         if (m_at->isRunning())
00844         {
00845             m_at->cancel();
00846             m_button->setLabel(QObject::tr("Test All"));
00847             if (m_frontleft)
00848                 m_frontleft->setEnabled(true);
00849             if (m_frontright)
00850                 m_frontright->setEnabled(true);
00851             if (m_center)
00852                 m_center->setEnabled(true);
00853             if (m_surroundleft)
00854                 m_surroundleft->setEnabled(true);
00855             if (m_surroundright)
00856                 m_surroundright->setEnabled(true);
00857             if (m_rearleft)
00858                 m_rearleft->setEnabled(true);
00859             if (m_rearright)
00860                 m_rearright->setEnabled(true);
00861             if (m_lfe)
00862                 m_lfe->setEnabled(true);
00863         }
00864         else
00865         {
00866             m_at->setChannel(-1);
00867             m_at->start();
00868             m_button->setLabel(QObject::tr("Stop"));
00869         }
00870         return;
00871     }
00872     if (m_at->isRunning())
00873     {
00874         m_at->cancel();
00875         m_at->wait();
00876     }
00877 
00878     int channel = str.toInt();
00879     m_at->setChannel(channel);
00880 
00881     m_at->start();
00882 }
00883 
00884 void AudioTest::togglequality()
00885 {
00886     if (m_at->isRunning())
00887     {
00888         toggle("start");
00889     }
00890 
00891     m_quality = m_hd->boolValue();
00892     delete m_at;
00893     m_at = new AudioTestThread(this, m_main, m_passthrough, m_channels,
00894                                m_settings, m_quality);
00895     if (!m_at->result().isEmpty())
00896     {
00897         QString msg = QObject::tr("Audio device is invalid or not useable.");
00898         MythPopupBox::showOkPopup(
00899             GetMythMainWindow(), QObject::tr("Warning"), msg);
00900     }
00901 }
00902 
00903 bool AudioTest::event(QEvent *event)
00904 {
00905     if (event->type() != ChannelChangedEvent::kEventType)
00906         return QObject::event(event); //not handled
00907 
00908     ChannelChangedEvent *cce = (ChannelChangedEvent*)(event);
00909     QString channel          = cce->channel;
00910 
00911     if (!cce->fulltest)
00912         return false;
00913 
00914     bool fl, fr, c, lfe, sl, sr, rl, rr;
00915     fl = fr = c = lfe = sl = sr = rl = rr = false;
00916 
00917     if (channel == "frontleft")
00918     {
00919         fl = true;
00920     }
00921     else if (channel == "frontright")
00922     {
00923         fr = true;
00924     }
00925     else if (channel == "center")
00926     {
00927         c = true;
00928     }
00929     else if (channel == "lfe")
00930     {
00931         lfe = true;
00932     }
00933     else if (channel == "surroundleft")
00934     {
00935         sl = true;
00936     }
00937     else if (channel == "surroundright")
00938     {
00939         sr = true;
00940     }
00941     else if (channel == "rearleft")
00942     {
00943         rl = true;
00944     }
00945     else if (channel == "rearright")
00946     {
00947         rr = true;
00948     }
00949     if (m_frontleft)
00950         m_frontleft->setEnabled(fl);
00951     if (m_frontright)
00952         m_frontright->setEnabled(fr);
00953     if (m_center)
00954         m_center->setEnabled(c);
00955     if (m_surroundleft)
00956         m_surroundleft->setEnabled(sl);
00957     if (m_surroundright)
00958         m_surroundright->setEnabled(sr);
00959     if (m_rearleft)
00960         m_rearleft->setEnabled(rl);
00961     if (m_rearright)
00962         m_rearright->setEnabled(rr);
00963     if (m_lfe)
00964         m_lfe->setEnabled(lfe);
00965     return false;
00966 }
00967 
00968 
00969 AudioTestGroup::AudioTestGroup(QString main, QString passthrough,
00970                                int channels, AudioOutputSettings settings)
00971 {
00972     addChild(new AudioTest(main, passthrough, channels, settings));
00973 }
00974 
00975 HostCheckBox *AudioMixerSettings::MythControlsVolume()
00976 {
00977     HostCheckBox *gc = new HostCheckBox("MythControlsVolume");
00978     gc->setLabel(QObject::tr("Use internal volume controls"));
00979     gc->setValue(true);
00980     gc->setHelpText(QObject::tr("If enabled, MythTV will control the PCM and "
00981                     "master mixer volume. Disable this option if you prefer "
00982                     "to control the volume externally (for example, using "
00983                     "your amplifier) or if you use an external mixer program."));
00984     return gc;
00985 }
00986 
00987 HostComboBox *AudioMixerSettings::MixerDevice()
00988 {
00989     HostComboBox *gc = new HostComboBox("MixerDevice", true);
00990     gc->setLabel(QObject::tr("Mixer device"));
00991 
00992 #ifdef USING_OSS
00993     QDir dev("/dev", "mixer*", QDir::Name, QDir::System);
00994     gc->fillSelectionsFromDir(dev);
00995 
00996     dev.setPath("/dev/sound");
00997     if (dev.exists())
00998     {
00999         gc->fillSelectionsFromDir(dev);
01000     }
01001 #endif
01002 #ifdef USING_ALSA
01003     gc->addSelection("ALSA:default", "ALSA:default");
01004 #endif
01005 #ifdef USING_MINGW
01006     gc->addSelection("DirectX:", "DirectX:");
01007     gc->addSelection("Windows:", "Windows:");
01008 #endif
01009 #if !defined(USING_MINGW)
01010     gc->addSelection("software", "software");
01011     gc->setHelpText(QObject::tr("Setting the mixer device to \"software\" "
01012                     "lets MythTV control the volume of all audio at the "
01013                     "expense of a slight quality loss."));
01014 #endif
01015 
01016     return gc;
01017 }
01018 
01019 const char* AudioMixerSettings::MixerControlControls[] = { "PCM",
01020                                                            "Master" };
01021 
01022 HostComboBox *AudioMixerSettings::MixerControl()
01023 {
01024     HostComboBox *gc = new HostComboBox("MixerControl", true);
01025     gc->setLabel(QObject::tr("Mixer controls"));
01026     for (unsigned int i = 0; i < sizeof(MixerControlControls) / sizeof(char*);
01027          ++i)
01028     {
01029         gc->addSelection(QObject::tr(MixerControlControls[i]),
01030                          MixerControlControls[i]);
01031     }
01032 
01033     gc->setHelpText(QObject::tr("Changing the volume adjusts the selected mixer."));
01034     return gc;
01035 }
01036 
01037 HostSlider *AudioMixerSettings::MixerVolume()
01038 {
01039     HostSlider *gs = new HostSlider("MasterMixerVolume", 0, 100, 1);
01040     gs->setLabel(QObject::tr("Master mixer volume"));
01041     gs->setValue(70);
01042     gs->setHelpText(QObject::tr("Initial volume for the Master mixer. "
01043                     "This affects all sound created by the audio device. "
01044                     "Note: Do not set this too low."));
01045     return gs;
01046 }
01047 
01048 HostSlider *AudioMixerSettings::PCMVolume()
01049 {
01050     HostSlider *gs = new HostSlider("PCMMixerVolume", 0, 100, 1);
01051     gs->setLabel(QObject::tr("PCM mixer volume"));
01052     gs->setValue(70);
01053     gs->setHelpText(QObject::tr("Initial volume for PCM output. Using the "
01054                     "volume keys in MythTV will adjust this parameter."));
01055     return gs;
01056 }
01057 
01058 AudioMixerSettings::AudioMixerSettings() :
01059     TriggeredConfigurationGroup(false, true, false, false)
01060 {
01061     setLabel(QObject::tr("Audio Mixer"));
01062     setUseLabel(false);
01063 
01064     Setting *volumeControl = MythControlsVolume();
01065     addChild(volumeControl);
01066 
01067         // Mixer settings
01068     ConfigurationGroup *settings =
01069         new VerticalConfigurationGroup(false, true, false, false);
01070     settings->addChild(MixerDevice());
01071     settings->addChild(MixerControl());
01072     settings->addChild(MixerVolume());
01073     settings->addChild(PCMVolume());
01074 
01075     ConfigurationGroup *dummy =
01076         new VerticalConfigurationGroup(false, true, false, false);
01077 
01078         // Show Mixer config only if internal volume controls enabled
01079     setTrigger(volumeControl);
01080     addTarget("0", dummy);
01081     addTarget("1", settings);
01082 }
01083 
01084 AudioGeneralSettings::AudioGeneralSettings()
01085 {
01086     addChild(new AudioConfigSettings(this));
01087     addChild(new AudioMixerSettings());
01088 }
01089 
01090 HostCheckBox *AudioAdvancedSettings::MPCM()
01091 {
01092     HostCheckBox *gc = new HostCheckBox("StereoPCM");
01093     gc->setLabel(QObject::tr("Stereo PCM Only"));
01094     gc->setValue(false);
01095     gc->setHelpText(QObject::tr("Enable if your amplifier or sound decoder "
01096                     "only supports 2 channel PCM (typically an old HDMI 1.0 "
01097                     "device). Multichannel audio will be re-encoded to AC-3 "
01098                     "when required"));
01099     return gc;
01100 }
01101 
01102 HostCheckBox *AudioAdvancedSettings::SRCQualityOverride()
01103 {
01104     HostCheckBox *gc = new HostCheckBox("SRCQualityOverride");
01105     gc->setLabel(QObject::tr("Override SRC quality"));
01106     gc->setValue(false);
01107     gc->setHelpText(QObject::tr("Enable to override audio sample rate "
01108                     "conversion quality."));
01109     return gc;
01110 }
01111 
01112 HostComboBox *AudioAdvancedSettings::SRCQuality()
01113 {
01114     HostComboBox *gc = new HostComboBox("SRCQuality", false);
01115     gc->setLabel(QObject::tr("Sample rate conversion"));
01116     gc->addSelection(QObject::tr("Disabled"), "-1");
01117     gc->addSelection(QObject::tr("Fastest"), "0");
01118     gc->addSelection(QObject::tr("Good"), "1", true); // default
01119     gc->addSelection(QObject::tr("Best"), "2");
01120     gc->setHelpText(QObject::tr("Set the quality of audio sample-rate "
01121                     "conversion. \"Good\" (default) provides the best "
01122                     "compromise between CPU usage and quality. \"Disabled\" "
01123                     "lets the audio device handle sample-rate conversion."));
01124     return gc;
01125 }
01126 
01127 HostCheckBox *AudioAdvancedSettings::Audio48kOverride()
01128 {
01129     HostCheckBox *gc = new HostCheckBox("Audio48kOverride");
01130     gc->setLabel(QObject::tr("Force audio device output to 48kHz"));
01131     gc->setValue(false);
01132     gc->setHelpText(QObject::tr("Force audio sample rate to 48kHz. "
01133                                 "Some audio devices will report various rates, "
01134                                 "but they ultimately crash."));
01135     return gc;
01136 }
01137 
01138 HostCheckBox *AudioAdvancedSettings::PassThroughOverride()
01139 {
01140     HostCheckBox *gc = new HostCheckBox("PassThruDeviceOverride");
01141     gc->setLabel(QObject::tr("Separate digital output device"));
01142     gc->setValue(false);
01143     gc->setHelpText(QObject::tr("Use a distinct digital output device from "
01144                                 "default."
01145                                 " (default is not checked)"));
01146     return gc;
01147 }
01148 
01149 HostComboBox *AudioAdvancedSettings::PassThroughOutputDevice()
01150 {
01151     HostComboBox *gc = new HostComboBox("PassThruOutputDevice", true);
01152 
01153     gc->setLabel(QObject::tr("Digital output device"));
01154     gc->addSelection(QObject::tr("Default"), "Default");
01155 #ifdef USING_MINGW
01156     gc->addSelection("DirectX:Primary Sound Driver");
01157 #else
01158     gc->addSelection("ALSA:iec958:{ AES0 0x02 }",
01159                      "ALSA:iec958:{ AES0 0x02 }");
01160     gc->addSelection("ALSA:hdmi", "ALSA:hdmi");
01161     gc->addSelection("ALSA:plughw:0,3", "ALSA:plughw:0,3");
01162 #endif
01163 
01164     gc->setHelpText(QObject::tr("Audio output device to use for "
01165                         "digital audio. This value is currently only used "
01166                         "with ALSA and DirectX sound output."));
01167     return gc;
01168 }
01169 
01170 HostCheckBox *AudioAdvancedSettings::SPDIFRateOverride()
01171 {
01172     HostCheckBox *gc = new HostCheckBox("SPDIFRateOverride");
01173     gc->setLabel(QObject::tr("SPDIF 48kHz rate override"));
01174     gc->setValue(false);
01175     gc->setHelpText(QObject::tr("ALSA only. By default, let ALSA determine "
01176                         "the passthrough sampling rate. If checked "
01177                         "set the sampling rate to 48kHz for passthrough."
01178                         " (default is not checked)"));
01179     return gc;
01180 }
01181 
01182 HostCheckBox *AudioAdvancedSettings::HBRPassthrough()
01183 {
01184     HostCheckBox *gc = new HostCheckBox("HBRPassthru");
01185     gc->setLabel(QObject::tr("HBR passthrough support"));
01186     gc->setValue(true);
01187     gc->setHelpText(QObject::tr("HBR support is required for TrueHD and DTS-HD "
01188                         "passthrough. If unchecked, Myth will limit the "
01189                         "passthrough bitrate to 6.144Mbit/s."
01190                         "This will disable True-HD passthrough, however will "
01191                         "allow DTS-HD content to be sent as DTS-HD Hi-Res."
01192                         " (default is checked)"));
01193     return gc;
01194 }
01195 
01196 AudioAdvancedSettings::AudioAdvancedSettings(bool mpcm)
01197 {
01198     ConfigurationGroup *settings3 =
01199         new HorizontalConfigurationGroup(false, false);
01200 
01201     m_PassThroughOverride = PassThroughOverride();
01202     TriggeredItem *sub3 =
01203         new TriggeredItem(m_PassThroughOverride, PassThroughOutputDevice());
01204     settings3->addChild(m_PassThroughOverride);
01205     settings3->addChild(sub3);
01206 
01207     ConfigurationGroup *settings4 =
01208         new HorizontalConfigurationGroup(false, false);
01209     Setting *srcqualityoverride = SRCQualityOverride();
01210     TriggeredItem *sub4 =
01211         new TriggeredItem(srcqualityoverride, SRCQuality());
01212     settings4->addChild(srcqualityoverride);
01213     settings4->addChild(sub4);
01214 
01215     ConfigurationGroup *settings5 =
01216         new HorizontalConfigurationGroup(false, false);
01217     settings5->addChild(Audio48kOverride());
01218 #if USING_ALSA
01219     settings5->addChild(SPDIFRateOverride());
01220 #endif
01221 
01222     ConfigurationGroup *settings6 =
01223         new HorizontalConfigurationGroup(false, false);
01224     settings6->addChild(HBRPassthrough());
01225 
01226     addChild(settings4);
01227     addChild(settings5);
01228     addChild(settings3);
01229     addChild(settings6);
01230 
01231     if (mpcm)
01232     {
01233         ConfigurationGroup *settings7;
01234         settings7 = new HorizontalConfigurationGroup(false, false);
01235         settings7->addChild(MPCM());
01236         addChild(settings7);
01237     }
01238 }
01239 
01240 AudioAdvancedSettingsGroup::AudioAdvancedSettingsGroup(bool mpcm)
01241 {
01242     addChild(new AudioAdvancedSettings(mpcm));
01243 }
01244 // vim:set sw=4 ts=4 expandtab:
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends