MythTV  master
audiooutput.cpp
Go to the documentation of this file.
1 #include <cstdio>
2 #include <cstdlib>
3 
4 using namespace std;
5 
6 // Qt utils: to parse audio list
7 #include <QFile>
8 #include <QDateTime>
9 #include <QDir>
10 
11 #include "mythconfig.h"
12 #include "audiooutput.h"
13 #include "mythmiscutil.h"
14 #include "compat.h"
15 
16 #include "audiooutputnull.h"
17 #ifdef _WIN32
18 #include "audiooutputdx.h"
19 #include "audiooutputwin.h"
20 #endif
21 #ifdef USING_OSS
22 #include "audiooutputoss.h"
23 #endif
24 #ifdef USING_ALSA
25 #include "audiooutputalsa.h"
26 #endif
27 #if CONFIG_DARWIN
28 #include "audiooutputca.h"
29 #endif
30 #ifdef USING_JACK
31 #include "audiooutputjack.h"
32 #endif
33 #ifdef USING_PULSEOUTPUT
34 #include "audiooutputpulse.h"
35 #endif
36 #ifdef USING_PULSE
37 #include "audiopulsehandler.h"
38 #endif
39 #ifdef Q_OS_ANDROID
40 #include "audiooutputopensles.h"
41 #include "audiooutputaudiotrack.h"
42 #endif
43 #ifdef USING_OPENMAX
44 #include "audiooutput_omx.h"
45 #endif
46 
47 extern "C" {
48 #include "libavcodec/avcodec.h" // to get codec id
49 }
50 #include "audioconvert.h"
51 
52 #define LOC QString("AO: ")
53 
55 {
56 #ifdef USING_PULSE
58 #endif
59 }
60 
62  const QString &main_device, const QString &passthru_device,
63  AudioFormat format, int channels, AVCodecID codec, int samplerate,
64  AudioOutputSource source, bool set_initial_vol, bool passthru,
65  int upmixer_startup, AudioOutputSettings *custom)
66 {
67  AudioSettings settings(
68  main_device, passthru_device, format, channels, codec, samplerate,
69  source, set_initial_vol, passthru, upmixer_startup, custom);
70 
71  return OpenAudio(settings);
72 }
73 
75  const QString &main_device, const QString &passthru_device,
76  bool willsuspendpa)
77 {
78  AudioSettings settings(main_device, passthru_device);
79 
80  return OpenAudio(settings, willsuspendpa);
81 }
82 
84  bool willsuspendpa)
85 {
86  QString &main_device = settings.m_main_device;
87  AudioOutput *ret = nullptr;
88 
89  // Don't suspend Pulse if unnecessary. This can save 100mS
90  if (settings.m_format == FORMAT_NONE || settings.m_channels <= 0)
91  willsuspendpa = false;
92 
93 #ifdef USING_PULSE
94  bool pulsestatus = false;
95 #else
96  {
97  static bool warned = false;
98  if (!warned && IsPulseAudioRunning())
99  {
100  warned = true;
101  LOG(VB_GENERAL, LOG_WARNING,
102  "WARNING: ***Pulse Audio is running***");
103  }
104  }
105 #endif
106 
107  settings.FixPassThrough();
108 
109  if (main_device.startsWith("PulseAudio:"))
110  {
111 #ifdef USING_PULSEOUTPUT
112  return new AudioOutputPulseAudio(settings);
113 #else
114  LOG(VB_GENERAL, LOG_ERR, "Audio output device is set to PulseAudio "
115  "but PulseAudio support is not compiled in!");
116  return nullptr;
117 #endif
118  }
119  if (main_device.startsWith("NULL"))
120  {
121  return new AudioOutputNULL(settings);
122  }
123 
124 #ifdef USING_PULSE
125  if (willsuspendpa)
126  {
127  bool ispulse = false;
128 #ifdef USING_ALSA
129  // Check if using ALSA, that the device doesn't contain the word
130  // "pulse" in its hint
131  if (main_device.startsWith("ALSA:"))
132  {
133  QString device_name = main_device;
134 
135  device_name.remove(0, 5);
136  QMap<QString, QString> *alsadevs =
138  if (!alsadevs->empty() && alsadevs->contains(device_name))
139  {
140  if (alsadevs->value(device_name).contains("pulse",
141  Qt::CaseInsensitive))
142  {
143  ispulse = true;
144  }
145  }
146  delete alsadevs;
147  }
148 #endif
149  if (main_device.contains("pulse", Qt::CaseInsensitive))
150  {
151  ispulse = true;
152  }
153  if (!ispulse)
154  {
156  }
157  }
158 #else // USING_PULSE
159  // Quiet warning error when not compiling with pulseaudio
160  Q_UNUSED(willsuspendpa);
161 #endif
162 
163  if (main_device.startsWith("ALSA:"))
164  {
165 #ifdef USING_ALSA
166  settings.TrimDeviceType();
167  ret = new AudioOutputALSA(settings);
168 #else
169  LOG(VB_GENERAL, LOG_ERR, "Audio output device is set to an ALSA device "
170  "but ALSA support is not compiled in!");
171 #endif
172  }
173  else if (main_device.startsWith("JACK:"))
174  {
175 #ifdef USING_JACK
176  settings.TrimDeviceType();
177  ret = new AudioOutputJACK(settings);
178 #else
179  LOG(VB_GENERAL, LOG_ERR, "Audio output device is set to a JACK device "
180  "but JACK support is not compiled in!");
181 #endif
182  }
183  else if (main_device.startsWith("DirectX:"))
184  {
185 #ifdef _WIN32
186  ret = new AudioOutputDX(settings);
187 #else
188  LOG(VB_GENERAL, LOG_ERR, "Audio output device is set to DirectX device "
189  "but DirectX support is not compiled in!");
190 #endif
191  }
192  else if (main_device.startsWith("Windows:"))
193  {
194 #ifdef _WIN32
195  ret = new AudioOutputWin(settings);
196 #else
197  LOG(VB_GENERAL, LOG_ERR, "Audio output device is set to a Windows "
198  "device but Windows support is not compiled "
199  "in!");
200 #endif
201  }
202  else if (main_device.startsWith("OpenSLES:"))
203  {
204 #ifdef Q_OS_ANDROID
205  ret = new AudioOutputOpenSLES(settings);
206 #else
207  LOG(VB_GENERAL, LOG_ERR, "Audio output device is set to a OpenSLES "
208  "device but Android support is not compiled "
209  "in!");
210 #endif
211  }
212  else if (main_device.startsWith("AudioTrack:"))
213  {
214 #ifdef Q_OS_ANDROID
215  ret = new AudioOutputAudioTrack(settings);
216 #else
217  LOG(VB_GENERAL, LOG_ERR, "Audio output device is set to AudioTrack "
218  "device but Android support is not compiled "
219  "in!");
220 #endif
221  }
222  else if (main_device.startsWith("OpenMAX:"))
223  {
224 #ifdef USING_OPENMAX
225  if (!getenv("NO_OPENMAX_AUDIO"))
226  ret = new AudioOutputOMX(settings);
227 #else
228  LOG(VB_GENERAL, LOG_ERR, "Audio output device is set to a OpenMAX "
229  "device but OpenMAX support is not compiled "
230  "in!");
231 #endif
232  }
233 #if defined(USING_OSS)
234  else
235  ret = new AudioOutputOSS(settings);
236 #elif CONFIG_DARWIN
237  else
238  ret = new AudioOutputCA(settings);
239 #endif
240 
241  if (!ret)
242  {
243  LOG(VB_GENERAL, LOG_CRIT, "No useable audio output driver found.");
244  LOG(VB_GENERAL, LOG_ERR, "Don't disable OSS support unless you're "
245  "not running on Linux.");
246 #ifdef USING_PULSE
247  if (pulsestatus)
249 #endif
250  return nullptr;
251  }
252 #ifdef USING_PULSE
253  ret->m_pulsewassuspended = pulsestatus;
254 #endif
255  return ret;
256 }
257 
259 {
260 #ifdef USING_PULSE
261  if (m_pulsewassuspended)
263 #endif
264  av_frame_free(&m_frame);
265 }
266 
267 void AudioOutput::SetStretchFactor(float /*factor*/)
268 {
269 }
270 
272 {
273  return new AudioOutputSettings;
274 }
275 
277 {
278  return new AudioOutputSettings;
279 }
280 
281 bool AudioOutput::CanPassthrough(int /*samplerate*/,
282  int /*channels*/,
283  AVCodecID /*codec*/,
284  int /*profile*/) const
285 {
286  return false;
287 }
288 
289 // TODO: get rid of this if possible... need to see what uses GetError() and
290 // GetWarning() and why. These would give more useful logs as macros
291 void AudioOutput::Error(const QString &msg)
292 {
293  m_lastError = msg;
294  LOG(VB_GENERAL, LOG_ERR, "AudioOutput Error: " + m_lastError);
295 }
296 
297 void AudioOutput::SilentError(const QString &msg)
298 {
299  m_lastError = msg;
300 }
301 
302 void AudioOutput::Warn(const QString &msg)
303 {
304  m_lastWarn = msg;
305  LOG(VB_GENERAL, LOG_WARNING, "AudioOutput Warning: " + m_lastWarn);
306 }
307 
309 {
310  m_lastError.clear();
311 }
312 
314 {
315  m_lastWarn.clear();
316 }
317 
319  QString &name, QString &desc, bool willsuspendpa)
320 {
321  AudioOutputSettings aosettings(true);
323 
324  AudioOutput *ao = OpenAudio(name, QString(), willsuspendpa);
325  if (ao)
326  {
327  aosettings = *(ao->GetOutputSettingsCleaned());
328  delete ao;
329  }
330  if (aosettings.IsInvalid())
331  {
332  if (!willsuspendpa)
333  return nullptr;
334  QString msg = tr("Invalid or unuseable audio device");
335  return new AudioOutput::AudioDeviceConfig(name, msg);
336  }
337 
338  QString capabilities = desc;
339  int max_channels = aosettings.BestSupportedChannelsELD();
340  if (aosettings.hasELD())
341  {
342  if (aosettings.getELD().isValid())
343  {
344  capabilities += tr(" (%1 connected to %2)")
345  .arg(aosettings.getELD().product_name().simplified())
346  .arg(aosettings.getELD().connection_name());
347  }
348  else
349  {
350  capabilities += tr(" (No connection detected)");
351  }
352  }
353 
354  QString speakers;
355  switch (max_channels)
356  {
357  case 6:
358  speakers = "5.1";
359  break;
360  case 8:
361  speakers = "7.1";
362  break;
363  default:
364  speakers = "2.0";
365  break;
366  }
367 
368  capabilities += tr("\nDevice supports up to %1")
369  .arg(speakers);
370  if (aosettings.canPassthrough() >= 0)
371  {
372  if (aosettings.hasELD() && aosettings.getELD().isValid())
373  {
374  // We have an ELD, show actual reported capabilities
375  capabilities += " (" + aosettings.getELD().codecs_desc() + ")";
376  }
377  else
378  {
379  // build capabilities string, in a similar fashion as reported
380  // by ELD
381  int mask = 0;
382  mask |=
383  (aosettings.canLPCM() << 0) |
384  (aosettings.canAC3() << 1) |
385  (aosettings.canDTS() << 2);
386  // cppcheck-suppress variableScope
387  static const char *type_names[] = { "LPCM", "AC3", "DTS" };
388 
389  if (mask != 0)
390  {
391  capabilities += QObject::tr(" (guessing: ");
392  bool found_one = false;
393  for (unsigned int i = 0; i < 3; i++)
394  {
395  if ((mask & (1 << i)) != 0)
396  {
397  if (found_one)
398  capabilities += ", ";
399  capabilities += type_names[i];
400  found_one = true;
401  }
402  }
403  capabilities += QString(")");
404  }
405  }
406  }
407  LOG(VB_AUDIO, LOG_INFO, QString("Found %1 (%2)")
408  .arg(name).arg(capabilities));
409  adc = new AudioOutput::AudioDeviceConfig(name, capabilities);
410  adc->m_settings = aosettings;
411  return adc;
412 }
413 
414 #ifdef USING_OSS
415 static void fillSelectionsFromDir(const QDir &dir,
416  AudioOutput::ADCVect *list)
417 {
418  QFileInfoList il = dir.entryInfoList();
419  for (QFileInfoList::Iterator it = il.begin();
420  it != il.end(); ++it )
421  {
422  QFileInfo &fi = *it;
423  QString name = fi.absoluteFilePath();
424  QString desc = AudioOutput::tr("OSS device");
427  if (!adc)
428  continue;
429  list->append(*adc);
430  delete adc;
431  }
432 }
433 #endif
434 
436 {
437  ADCVect *list = new ADCVect;
438  AudioDeviceConfig *adc;
439 
440 #ifdef USING_PULSE
442 #endif
443 
444 #ifdef USING_ALSA
445  QMap<QString, QString> *alsadevs = AudioOutputALSA::GetDevices("pcm");
446 
447  if (!alsadevs->empty())
448  {
449  for (QMap<QString, QString>::const_iterator i = alsadevs->begin();
450  i != alsadevs->end(); ++i)
451  {
452  const QString& key = i.key();
453  QString desc = i.value();
454  QString devname = QString("ALSA:%1").arg(key);
455 
456  adc = GetAudioDeviceConfig(devname, desc);
457  if (!adc)
458  continue;
459  list->append(*adc);
460  delete adc;
461  }
462  }
463  delete alsadevs;
464 #endif
465 #ifdef USING_OSS
466  {
467  QDir dev("/dev", "dsp*", QDir::Name, QDir::System);
468  fillSelectionsFromDir(dev, list);
469  dev.setNameFilters(QStringList("adsp*"));
470  fillSelectionsFromDir(dev, list);
471 
472  dev.setPath("/dev/sound");
473  if (dev.exists())
474  {
475  dev.setNameFilters(QStringList("dsp*"));
476  fillSelectionsFromDir(dev, list);
477  dev.setNameFilters(QStringList("adsp*"));
478  fillSelectionsFromDir(dev, list);
479  }
480  }
481 #endif
482 #ifdef USING_JACK
483  {
484  QString name = "JACK:";
485  QString desc = tr("Use JACK default sound server.");
486  adc = GetAudioDeviceConfig(name, desc);
487  if (adc)
488  {
489  list->append(*adc);
490  delete adc;
491  }
492  }
493 #endif
494 #if CONFIG_DARWIN
495 
496  {
497  QMap<QString, QString> *devs = AudioOutputCA::GetDevices(nullptr);
498  if (!devs->empty())
499  {
500  for (QMap<QString, QString>::const_iterator i = devs->begin();
501  i != devs->end(); ++i)
502  {
503  QString key = i.key();
504  QString desc = i.value();
505  QString devname = QString("CoreAudio:%1").arg(key);
506 
507  adc = GetAudioDeviceConfig(devname, desc);
508  if (!adc)
509  continue;
510  list->append(*adc);
511  delete adc;
512  }
513  }
514  delete devs;
515  QString name = "CoreAudio:Default Output Device";
516  QString desc = tr("CoreAudio default output");
517  adc = GetAudioDeviceConfig(name, desc);
518  if (adc)
519  {
520  list->append(*adc);
521  delete adc;
522  }
523  }
524 #endif
525 #ifdef _WIN32
526  {
527  QString name = "Windows:";
528  QString desc = "Windows default output";
529  adc = GetAudioDeviceConfig(name, desc);
530  if (adc)
531  {
532  list->append(*adc);
533  delete adc;
534  }
535 
536  QMap<int, QString> *dxdevs = AudioOutputDX::GetDXDevices();
537 
538  if (!dxdevs->empty())
539  {
540  for (QMap<int, QString>::const_iterator i = dxdevs->begin();
541  i != dxdevs->end(); ++i)
542  {
543  QString devdesc = i.value();
544  QString devname = QString("DirectX:%1").arg(devdesc);
545 
546  adc = GetAudioDeviceConfig(devname, devdesc);
547  if (!adc)
548  continue;
549  list->append(*adc);
550  delete adc;
551  }
552  }
553  delete dxdevs;
554  }
555 #endif
556 
557 #ifdef USING_PULSE
558  if (pasuspended)
560 #endif
561 
562 #ifdef USING_PULSEOUTPUT
563  {
564  QString name = "PulseAudio:default";
565  QString desc = tr("PulseAudio default sound server.");
566  adc = GetAudioDeviceConfig(name, desc);
567  if (adc)
568  {
569  list->append(*adc);
570  delete adc;
571  }
572  }
573 #endif
574 
575 #ifdef ANDROID
576  {
577  QString name = "OpenSLES:";
578  QString desc = tr("OpenSLES default output. Stereo support only.");
579  adc = GetAudioDeviceConfig(name, desc);
580  if (adc)
581  {
582  list->append(*adc);
583  delete adc;
584  }
585  }
586  {
587  QString name = "AudioTrack:";
588  QString desc = tr("Android AudioTrack output. Supports surround sound.");
589  adc = GetAudioDeviceConfig(name, desc);
590  if (adc)
591  {
592  list->append(*adc);
593  delete adc;
594  }
595  }
596 #endif
597 
598 #ifdef USING_OPENMAX
599  if (!getenv("NO_OPENMAX_AUDIO"))
600  {
601  QString name = "OpenMAX:analog";
602  QString desc = tr("OpenMAX analog output.");
603  adc = GetAudioDeviceConfig(name, desc);
604  if (adc)
605  {
606  list->append(*adc);
607  delete adc;
608  }
609 
610  name = "OpenMAX:hdmi";
611  desc = tr("OpenMAX HDMI output.");
612  adc = GetAudioDeviceConfig(name, desc);
613  if (adc)
614  {
615  list->append(*adc);
616  delete adc;
617  }
618  }
619 #endif
620 
621  QString name = "NULL";
622  QString desc = "NULL device";
623  adc = GetAudioDeviceConfig(name, desc);
624  if (adc)
625  {
626  list->append(*adc);
627  delete adc;
628  }
629  return list;
630 }
631 
639 int AudioOutput::DecodeAudio(AVCodecContext *ctx,
640  uint8_t *buffer, int &data_size,
641  const AVPacket *pkt)
642 {
643  bool got_frame = false;
644  int ret;
645  char error[AV_ERROR_MAX_STRING_SIZE];
646 
647  data_size = 0;
648  if (!m_frame)
649  {
650  if (!(m_frame = av_frame_alloc()))
651  {
652  return AVERROR(ENOMEM);
653  }
654  }
655  else
656  {
657  av_frame_unref(m_frame);
658  }
659 
660 // SUGGESTION
661 // Now that avcodec_decode_audio4 is deprecated and replaced
662 // by 2 calls (receive frame and send packet), this could be optimized
663 // into separate routines or separate threads.
664 // Also now that it always consumes a whole buffer some code
665 // in the caller may be able to be optimized.
666  ret = avcodec_receive_frame(ctx,m_frame);
667  if (ret == 0)
668  got_frame = true;
669  if (ret == AVERROR(EAGAIN))
670  ret = 0;
671  if (ret == 0)
672  ret = avcodec_send_packet(ctx, pkt);
673  if (ret == AVERROR(EAGAIN))
674  ret = 0;
675  else if (ret < 0)
676  {
677  LOG(VB_AUDIO, LOG_ERR, LOC +
678  QString("audio decode error: %1 (%2)")
679  .arg(av_make_error_string(error, sizeof(error), ret))
680  .arg(got_frame));
681  return ret;
682  }
683  else
684  ret = pkt->size;
685 
686  if (!got_frame)
687  {
688  LOG(VB_AUDIO, LOG_DEBUG, LOC +
689  QString("audio decode, no frame decoded (%1)").arg(ret));
690  return ret;
691  }
692 
693  AVSampleFormat format = (AVSampleFormat)m_frame->format;
694  AudioFormat fmt =
695  AudioOutputSettings::AVSampleFormatToFormat(format, ctx->bits_per_raw_sample);
696 
697  data_size = m_frame->nb_samples * m_frame->channels * av_get_bytes_per_sample(format);
698 
699  // May need to convert audio to S16
700  AudioConvert converter(fmt, CanProcess(fmt) ? fmt : FORMAT_S16);
701  uint8_t* src;
702 
703  if (av_sample_fmt_is_planar(format))
704  {
705  src = buffer;
706  converter.InterleaveSamples(m_frame->channels,
707  src,
708  (const uint8_t **)m_frame->extended_data,
709  data_size);
710  }
711  else
712  {
713  // data is already compacted...
714  src = m_frame->extended_data[0];
715  }
716 
717  uint8_t* transit = buffer;
718 
719  if (!CanProcess(fmt) &&
720  av_get_bytes_per_sample(ctx->sample_fmt) < AudioOutputSettings::SampleSize(converter.Out()))
721  {
722  // this conversion can't be done in place
723  transit = (uint8_t*)av_malloc(data_size * av_get_bytes_per_sample(ctx->sample_fmt)
724  / AudioOutputSettings::SampleSize(converter.Out()));
725  if (!transit)
726  {
727  LOG(VB_AUDIO, LOG_ERR, LOC +
728  QString("audio decode, out of memory"));
729  data_size = 0;
730  return ret;
731  }
732  }
733  if (!CanProcess(fmt) || src != transit)
734  {
735  data_size = converter.Process(transit, src, data_size, true);
736  }
737  if (transit != buffer)
738  {
739  av_free(transit);
740  }
741  return ret;
742 }
QString m_main_device
Definition: audiosettings.h:65
void Warn(const QString &msg)
virtual AudioOutputSettings * GetOutputSettingsUsers(bool digital=true)
void Error(const QString &msg)
bool IsInvalid()
return true if class instance is marked invalid.
QString codecs_desc()
Definition: eldutils.cpp:507
static void error(const char *str,...)
Definition: vbi.c:42
QVector< AudioDeviceConfig > ADCVect
Definition: audiooutput.h:45
static AudioOutput * OpenAudio(const QString &main_device, const QString &passthru_device, AudioFormat format, int channels, AVCodecID codec, int samplerate, AudioOutputSource source, bool set_initial_vol, bool passthru, int upmixer_startup=0, AudioOutputSettings *custom=nullptr)
Definition: audiooutput.cpp:61
static QMap< QString, QString > * GetDevices(const char *type)
AudioFormat m_format
Definition: audiosettings.h:67
QString connection_name()
Definition: eldutils.cpp:475
virtual bool CanPassthrough(int samplerate, int channels, AVCodecID codec, int profile) const
static QMap< QString, QString > * GetDevices(const char *type=nullptr)
int BestSupportedChannelsELD()
Reports best supported channel number, restricted to ELD range.
int Process(void *out, const void *in, int bytes, bool noclip=false)
Process Parameters: out : destination buffer where converted samples will be copied in : source buffe...
AudioOutputSettings m_settings
Definition: audiooutput.h:32
bool m_pulsewassuspended
Definition: audiooutput.h:203
virtual AudioOutputSettings * GetOutputSettingsCleaned(bool digital=true)
#define LOC
Definition: audiooutput.cpp:52
void TrimDeviceType(void)
QString product_name()
Definition: eldutils.cpp:470
static QMap< int, QString > * GetDXDevices(void)
static int SampleSize(AudioFormat format)
AudioFormat Out(void)
Definition: audioconvert.h:49
ELD & getELD(void)
retrieve ELD data
void InterleaveSamples(int channels, uint8_t *output, const uint8_t *const *input, int data_size)
bool IsPulseAudioRunning(void)
Is A/V Sync destruction daemon is running on this host?
const char * name
Definition: ParseText.cpp:328
virtual void SetStretchFactor(float factor)
virtual int DecodeAudio(AVCodecContext *ctx, uint8_t *buffer, int &data_size, const AVPacket *pkt)
Utility routine.
void * av_malloc(unsigned int size)
static AudioDeviceConfig * GetAudioDeviceConfig(QString &name, QString &desc, bool willsuspendpa=false)
static bool Suspend(enum PulseAction action)
AudioOutputSource
Definition: audiosettings.h:17
void SilentError(const QString &msg)
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
~AudioOutput() override
bool canLPCM()
return true if device supports multichannels PCM (deprecated, see canFeature())
static void Cleanup(void)
Definition: audiooutput.cpp:54
void ClearWarning(void)
bool isValid()
Definition: eldutils.cpp:438
Implements Core Audio (Mac OS X Hardware Abstraction Layer) output.
Definition: audiooutputca.h:13
void av_free(void *ptr)
void ClearError(void)
bool hasELD()
get the ELD flag
static AudioFormat AVSampleFormatToFormat(AVSampleFormat format, int bits=0)
Return AVSampleFormat closest equivalent to AudioFormat.
bool canDTS()
return true if device can or may support DTS (deprecated, see canFeature())
static ADCVect * GetOutputList(void)
bool canAC3()
return true if device can or may support AC3 (deprecated, see canFeature())
static void fillSelectionsFromDir(const QDir &dir, AudioOutput::ADCVect *list)
void FixPassThrough(void)