Permalink
Browse files

Add hdmi audio device type detection

This commit add ELD (EDID Like Data) detection. It allows myth to detect the various capabilities of the audio device connected via HDMI or DisplayPort.
At present, this information is informative only (except for StereoPCM) and is displayed in the device description in the Audio Settings screen.
It will also show if a device is connected or not on the HDMI or DP port making it much easier to identify which HDMI ALSA device to use (there are often 3 presented, when only one is actually working)

To benefit from this option, you must run alsa drivers >= 1.0.25 or linux kernel 3.2 or later.

StereoPCM advanced user option is now detected automatically if the device ELD is available.
Setting it in the advanded settings will only override this option. As the worse side effect should it be detected improperly is that AC3 re-encoding will occur; I believe the benefits overweigh the downside (if there are any).

This commit includes other minor cosmetic fixes, such as allowing translating the default audio device names in the audio setup screen.

In the future, should ELD be available, the aim is to completely remove the use for audio user settings when using HDMI or DP audio.
  • Loading branch information...
jyavenard committed Feb 19, 2012
1 parent 459adc2 commit 30c0773918e461cb93a1b63aee4fedc0ec91d04e
@@ -286,7 +286,22 @@ AudioOutput::AudioDeviceConfig* AudioOutput::GetAudioDeviceConfig(
}
}
int max_channels = aosettings.BestSupportedChannels();
QString capabilities = desc;
int max_channels = aosettings.BestSupportedChannelsELD();
if (aosettings.hasELD())
{
if (aosettings.getELD().isValid())
{
capabilities += QObject::tr(" (%1 connected to %2)")
.arg(aosettings.getELD().product_name().simplified())
.arg(aosettings.getELD().connection_name());
}
else
{
capabilities += QObject::tr(" (No connection detected)");
}
}
QString speakers;
switch (max_channels)
{
@@ -300,20 +315,44 @@ AudioOutput::AudioDeviceConfig* AudioOutput::GetAudioDeviceConfig(
speakers = "2.0";
break;
}
QString capabilities = desc + QString("\nDevice supports up to %1")
.arg(speakers);
capabilities += QObject::tr("\nDevice supports up to %1")
.arg(speakers);
if (aosettings.canPassthrough() >= 0)
{
capabilities += QString(" (");
if (aosettings.canPassthrough() > 0)
capabilities += QString("digital output, ");
if (aosettings.canAC3())
capabilities += QString("AC3,");
if (aosettings.canDTS())
capabilities += QString("DTS,");
if (aosettings.canLPCM())
capabilities += QString("multi-channels LPCM");
capabilities += QString(")");
if (aosettings.hasELD() && aosettings.getELD().isValid())
{
// We have an ELD, show actual reported capabilities
capabilities += " (" + aosettings.getELD().codecs_desc() + ")";
}
else
{
// build capabilities string, in a similar fashion as reported
// by ELD
int mask = 0;
mask |=
(aosettings.canLPCM() << 0) |
(aosettings.canAC3() << 1) |
(aosettings.canDTS() << 2);
static const char *type_names[] = { "LPCM", "AC3", "DTS" };
if (mask != 0)
{
capabilities += QObject::tr(" (guessing: ");
bool found_one = false;
for (unsigned int i = 0; i < 3; i++)
{
if ((mask & (1 << i)) != 0)
{
if (found_one)
capabilities += ", ";
capabilities += type_names[i];
found_one = true;
}
}
capabilities += QString(")");
}
}
}
LOG(VB_AUDIO, LOG_INFO, QString("Found %1 (%2)")
.arg(name).arg(capabilities));
@@ -332,7 +371,7 @@ static void fillSelectionsFromDir(const QDir &dir,
{
QFileInfo &fi = *it;
QString name = fi.absoluteFilePath();
QString desc = QString("OSS device");
QString desc = QObject::tr("OSS device");
AudioOutput::AudioDeviceConfig *adc =
AudioOutput::GetAudioDeviceConfig(name, desc);
if (!adc)
@@ -393,7 +432,7 @@ AudioOutput::ADCVect* AudioOutput::GetOutputList(void)
#ifdef USING_JACK
{
QString name = "JACK:";
QString desc = "Use JACK default sound server.";
QString desc = QObject::tr("Use JACK default sound server.");
adc = GetAudioDeviceConfig(name, desc);
if (adc)
{
@@ -414,7 +453,7 @@ AudioOutput::ADCVect* AudioOutput::GetOutputList(void)
QString key = i.key();
QString desc = i.value();
QString devname = QString("CoreAudio:%1").arg(key);
adc = GetAudioDeviceConfig(devname, desc);
if (!adc)
continue;
@@ -424,7 +463,7 @@ AudioOutput::ADCVect* AudioOutput::GetOutputList(void)
}
delete devs;
QString name = "CoreAudio:Default Output Device";
QString desc = "CoreAudio default output";
QString desc = QObject::tr("CoreAudio default output");
adc = GetAudioDeviceConfig(name, desc);
if (adc)
{
@@ -473,7 +512,7 @@ AudioOutput::ADCVect* AudioOutput::GetOutputList(void)
#ifdef USING_PULSEOUTPUT
{
QString name = "PulseAudio:default";
QString desc = QString("PulseAudio default sound server. ");
QString desc = QObject::tr("PulseAudio default sound server.");
adc = GetAudioDeviceConfig(name, desc);
if (adc)
{
@@ -287,6 +287,82 @@ bool AudioOutputALSA::IncPreallocBufferSize(int requested, int buffer_time)
return ret;
}
QByteArray *AudioOutputALSA::GetELD(int card, int device, int subdevice)
{
QByteArray *result = NULL;
snd_hctl_t *hctl;
snd_hctl_elem_t *elem;
snd_ctl_elem_info_t *info; snd_ctl_elem_info_alloca(&info);
snd_ctl_elem_id_t *id; snd_ctl_elem_id_alloca(&id);
snd_ctl_elem_value_t *control; snd_ctl_elem_value_alloca(&control);
snd_ctl_elem_type_t type;
unsigned int count;
int err;
snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_PCM);
snd_ctl_elem_id_set_name(id, "ELD");
snd_ctl_elem_id_set_device(id, device);
if ((err = snd_hctl_open(&hctl,
QString("hw:%1").arg(card).toAscii().constData(),
0)) < 0)
{
VBAUDIO(QString("Control %1 open error: %2")
.arg(card)
.arg(snd_strerror(err)));
return NULL;
}
if ((err = snd_hctl_load(hctl)) < 0)
{
VBAUDIO(QString("Control %1 load error: %2")
.arg(card)
.arg(snd_strerror(err)));
/* frees automatically the control which cannot be added. */
return NULL;
}
elem = snd_hctl_find_elem(hctl, id);
if (elem)
{
if ((err = snd_hctl_elem_info(elem, info)) < 0)
{
VBAUDIO(QString("Control %1 snd_hctl_elem_info error: %2")
.arg(card)
.arg(snd_strerror(err)));
snd_hctl_close(hctl);
return NULL;
}
count = snd_ctl_elem_info_get_count(info);
type = snd_ctl_elem_info_get_type(info);
if (!snd_ctl_elem_info_is_readable(info))
{
VBAUDIO(QString("Control %1 element info is not readable")
.arg(card));
snd_hctl_close(hctl);
return NULL;
}
if ((err = snd_hctl_elem_read(elem, control)) < 0)
{
VBAUDIO(QString("Control %1 element read error: %2")
.arg(card)
.arg(snd_strerror(err)));
snd_hctl_close(hctl);
return NULL;
}
if (type != SND_CTL_ELEM_TYPE_BYTES)
{
VBAUDIO(QString("Control %1 element is of the wrong type")
.arg(card));
snd_hctl_close(hctl);
return NULL;
}
result = new QByteArray((char *)snd_ctl_elem_value_get_bytes(control),
count);
}
snd_hctl_close(hctl);
return result;
}
AudioOutputSettings* AudioOutputALSA::GetOutputSettings(bool passthrough)
{
snd_pcm_hw_params_t *params;
@@ -357,6 +433,23 @@ AudioOutputSettings* AudioOutputALSA::GetOutputSettings(bool passthrough)
if (snd_pcm_hw_params_test_channels(pcm_handle, params, channels) >= 0)
settings->AddSupportedChannels(channels);
int card, device, subdevice;
if (GetPCMInfo(card, device, subdevice) >= 0)
{
// Check if we can retrieve ELD for this device
QByteArray *eld = GetELD(card, device, subdevice);
if (eld != NULL)
{
VBAUDIO(QString("Successfully retrieved ELD data"));
settings->setELD(eld);
delete eld;
}
}
else
{
VBAUDIO("Can't get card and device number");
}
snd_pcm_close(pcm_handle);
pcm_handle = NULL;
@@ -38,6 +38,7 @@ class AudioOutputALSA : public AudioOutputBase
inline int SetParameters(snd_pcm_t *handle, snd_pcm_format_t format,
uint channels, uint rate, uint buffer_time,
uint period_time);
QByteArray *GetELD(int card, int device, int subdevice);
// Volume related
bool OpenMixer(void);
@@ -17,12 +17,13 @@ using namespace std;
extern "C" {
#include "libavutil/avutil.h" // to check version of libavformat
}
#include "eldutils.h"
#define LOC QString("AOS: ")
AudioOutputSettings::AudioOutputSettings(bool invalid) :
m_passthrough(-1), m_features(FEATURE_NONE),
m_invalid(invalid), m_has_eld(false)
m_invalid(invalid), m_has_eld(false), m_eld(ELD())
{
m_sr.assign(srs, srs +
sizeof(srs) / sizeof(int));
@@ -53,8 +54,9 @@ AudioOutputSettings& AudioOutputSettings::operator=(
m_channels = rhs.m_channels;
m_passthrough = rhs.m_passthrough;
m_features = rhs.m_features;
m_has_eld = rhs.m_has_eld;
m_invalid = rhs.m_invalid;
m_has_eld = rhs.m_has_eld;
m_eld = rhs.m_eld;
m_sr_it = m_sr.begin() + (rhs.m_sr_it - rhs.m_sr.begin());
m_sf_it = m_sf.begin() + (rhs.m_sf_it - rhs.m_sf.begin());
return *this;
@@ -253,14 +255,19 @@ void AudioOutputSettings::SetBestSupportedChannels(int channels)
m_channels.push_back(channels);
}
void AudioOutputSettings::setFeature(bool val, DigitalFeature arg)
void AudioOutputSettings::setFeature(bool val, int arg)
{
if (val)
m_features |= arg;
else
m_features &= ~arg;
};
void AudioOutputSettings::setFeature(bool val, DigitalFeature arg)
{
setFeature(val, (int) arg);
};
/**
* Returns capabilities supported by the audio device
* amended to take into account the digital audio
@@ -282,9 +289,7 @@ AudioOutputSettings* AudioOutputSettings::GetCleaned(bool newcopy)
if (m_invalid)
return aosettings;
int mchannels = BestSupportedChannels();
if (mchannels > 2)
if (BestSupportedPCMChannelsELD() > 2)
{
aosettings->setFeature(FEATURE_LPCM);
}
@@ -301,14 +306,22 @@ AudioOutputSettings* AudioOutputSettings::GetCleaned(bool newcopy)
//#endif
if (m_passthrough >= 0)
{
if (mchannels == 2)
if (BestSupportedChannels() == 2)
{
LOG(VB_AUDIO, LOG_INFO, LOC + "may be AC3 or DTS capable");
aosettings->AddSupportedChannels(6);
}
aosettings->setFeature(FEATURE_AC3 | FEATURE_DTS);
}
}
else
{
// Can't do digital passthrough without 16 bits audio
aosettings->setPassthrough(-1);
aosettings->setFeature(false,
FEATURE_AC3 | FEATURE_DTS | FEATURE_LPCM |
FEATURE_EAC3 | FEATURE_TRUEHD | FEATURE_DTSHD);
}
return aosettings;
}
@@ -499,3 +512,37 @@ QString AudioOutputSettings::GetPassthroughParams(int codec, int codec_profile,
return log;
}
bool AudioOutputSettings::hasValidELD()
{
return m_has_eld && m_eld.isValid();
};
bool AudioOutputSettings::hasELD()
{
return m_has_eld;
};
void AudioOutputSettings::setELD(QByteArray *ba)
{
m_has_eld = true;
m_eld = ELD(ba->constData(), ba->size());
m_eld.show();
}
int AudioOutputSettings::BestSupportedChannelsELD()
{
int chan = AudioOutputSettings::BestSupportedChannels();
if (!hasValidELD())
return chan;
int eldc = m_eld.maxChannels();
return eldc < chan ? eldc : chan;
}
int AudioOutputSettings::BestSupportedPCMChannelsELD()
{
int chan = AudioOutputSettings::BestSupportedChannels();
if (!hasValidELD())
return chan;
int eldc = m_eld.maxLPCMChannels();
return eldc < chan ? eldc : chan;
}
Oops, something went wrong.

0 comments on commit 30c0773

Please sign in to comment.