popcornmix / omxplayer

omxplayer
GNU General Public License v2.0
1.02k stars 334 forks source link

Simulataneous Audio Over ALSA and Local #498

Open scotthardwick opened 7 years ago

scotthardwick commented 7 years ago

I have been researching on Google and I seem to keep coming up that at least in the past it was not possible to get simultaneous audio to HDMI and I2C.

Is there a way to get simultaneous output to Local jack and ALSA (in my case I am using a Hifiberry Amp+)

Thanks for any input!

Best!

Scott

popcornmix commented 7 years ago

Not currently possible. Some discussion here: https://github.com/popcornmix/omxplayer/pull/463 @fabled may be explain exactly what is still required.

fabled commented 7 years ago

Well, pretty much all of the code works. It would just require specifying the command line parameters and implementing the small bit of glue code. The format it was originally was not good enough, so I'm open for suggestions. The current options are: local, hdmi, both and alsa. We'd need something to make both use alsa, or add new target name such as 'alsa+hdmi' to specify output on both. Suggestions?

popcornmix commented 7 years ago

-o alsa+hdmi is good for me.

scotthardwick commented 7 years ago

My personal thanks to both of you! Your coding skills and knowledge of how all this truly is beyond impressive to someone who is much better categorized as a "novice".

-o local -o hdmi -o hdmi+local (if you want to alias "both") -o alsa -o alsa+local -o alsa+hdmi

scotthardwick commented 7 years ago

Fabled--- Could you please also add Alsa+local support? Thank You!

fabled commented 7 years ago

@scotthardwick alsa+local is slightly more complex. It would require setting up additional OMX switching logic. Also I wonder should alsa or local output be the clock master. @popcornmix Care to review the pull request for alsa+hdmi? Any suggestions how to do splitting and clocking for alsa+local output? Would we also need alsa+local+hdmi?

scotthardwick commented 7 years ago

@fabled -- I would think alsa would be the clock master in that situation. But that is just my gut thought. Thank you for continuing to investigate this for me!

jehutting commented 7 years ago

I have been hesitating to comment on this as I was already late at the party... But still seeing that it is not implemented/pulled in yet, I will give my comment for what it is worth :-)

Isn't it better to have a comma separated list of devices you want to use? E.g. -o local,hdmi for both the local and hdmi, or -o alsa:snd2,local for both local and alsa device named snd2.

The notation alsa+hdmi[:device] looks more specifying the hdmi device, whereas specifying the alsa device is meant.

fabled commented 7 years ago

@jehutting Sounds good to me. @popcornmix Any suggestions how to modify to code the pass a list of sound outputs instead of single device name?

popcornmix commented 7 years ago

I'm happy with @jehutting's syntax. I think I'd probably make the OMXAudioConfig.device become the comma separated string. I'd remove the "omx:" prefix as I don't see that being essential for the omxplayer.cpp -> OMXAudio.cpp API. Existing command line option -o both would behave the same as -o hdmi,local so internally I'd pass hdmi,local and treat both just as a shortcut that is expanded by omxplayer.cpp. Code like

  if (m_config.device == "omx:both" || m_config.device == "omx:hdmi")
  {
    if(!m_omx_render_hdmi.Initialize("OMX.broadcom.audio_render", OMX_IndexParamAudioInit))
      return false;
  }

would become

  if (m_config.device.find("hdmi") != std::string::npos)
  {
    if(!m_omx_render_hdmi.Initialize("OMX.broadcom.audio_render", OMX_IndexParamAudioInit))
      return false;
  }

(a helper function for checking if m_config.device enables "hdmi" etc is also valid) Up to you if you now parse the alsa subdevice from the device string inside OMXAudio.cpp (perhaps with a helper function) rather than using subdevice.

jehutting commented 7 years ago

I'm using (in my own, not-gihub'ed, not public'ed) omxplayer the following code:

  // audio out device
  {
  std::vector<std::string> devices;
  boost::split(devices, settings.device_string, boost::is_any_of(","), boost::token_compress_on);

  m_config_audio.devices = OMXAudioConfig::AudioDevice::NONE;
  for(auto it = devices.begin(); it != devices.end(); it++)
  {
    #define device (*it)
    if(device == "both")
      m_config_audio.devices |= (OMXAudioConfig::AudioDevice::LOCAL|OMXAudioConfig::AudioDevice::HDMI);
    else if(device == "local")
      m_config_audio.devices |= OMXAudioConfig::AudioDevice::LOCAL;
    else if(device == "hdmi")
      m_config_audio.devices |= OMXAudioConfig::AudioDevice::HDMI;
    else if(device.substr(0, 4) == "alsa")
    {
      m_config_audio.devices |= OMXAudioConfig::AudioDevice::ALSA;
      // check for alsa device name
      if(device[4] == ':')
        m_config_audio.alsa_device_name = device.substr(4+1/* to skip ':' */);
    }
  }
  }

Changed to using string.find() -just for fun-:

  // audio out device
  m_config_audio.devices = OMXAudioConfig::AudioDevice::NONE;
  if(settings.device_string.find("both") != std::string::npos)
    m_config_audio.devices = (OMXAudioConfig::AudioDevice::LOCAL|OMXAudioConfig::AudioDevice::HDMI);
  else 
  {
    if(settings.device_string.find("local") != std::string::npos)
      m_config_audio.devices = OMXAudioConfig::AudioDevice::LOCAL;
    if(settings.device_string.find("hdmi") != std::string::npos)
      m_config_audio.devices |= OMXAudioConfig::AudioDevice::HDMI;
  }
  size_t pos = settings.device_string.find("alsa");
  if(pos != std::string::npos)
  {
    m_config_audio.devices |= OMXAudioConfig::AudioDevice::ALSA;
    pos += 4; // position right behind "alsa"
    // check for alsa device name
    if(settings.device_string[pos] == ':')
    {
      pos++; // name without the ':' character
      size_t end = settings.device_string.find(',', pos);  
      if(end == std::string::npos) // get name till end of device_string...
        m_config_audio.alsa_device_name = settings.device_string.substr(pos);
      else // ...or till (found) separator
        m_config_audio.alsa_device_name = settings.device_string.substr(pos, end-pos);
    }
  }

The AudioDevice class defined in OMXAudio.h:

class OMXAudioConfig
{
public:
  enum class AudioDevice : std::uint8_t { NONE=0x00, LOCAL=0x01, HDMI=0x02, ALSA=0x04 };
  friend bool operator&(AudioDevice lhs, AudioDevice rhs) { return ((uint8_t)lhs & (uint8_t)rhs) ? true : false; }
  friend AudioDevice operator|(const AudioDevice lhs, const AudioDevice rhs) { return static_cast<AudioDevice>((uint8_t)lhs | (uint8_t)rhs); }
  friend AudioDevice& operator|=(AudioDevice& lhs, const AudioDevice rhs) { return lhs = static_cast<AudioDevice>((uint8_t)lhs | (uint8_t)rhs); }

  COMXStreamInfo hints;
  AudioDevice devices;
  std::string alsa_device_name;
  ...

  OMXAudioConfig()
  {
    devices = AudioDevice::NONE;
    alsa_device_name = "default";
    ...
  }
};

A snapshot of its usage (in OMXAudio.cpp)

  //   S P L I T T E R   I N I T I A L I S A T I O N
  // Only when two or more render components are used

  int ndevices = 0;
  if(m_config.devices & OMXAudioConfig::AudioDevice::LOCAL)
    ndevices++;
  if(m_config.devices & OMXAudioConfig::AudioDevice::HDMI)
    ndevices++;
  if(m_config.devices & OMXAudioConfig::AudioDevice::ALSA)
    ndevices++;
  if(ndevices > 1)
  {
    if(!m_omx_splitter.Initialize("OMX.broadcom.audio_splitter", OMX_IndexParamAudioInit))
    {
      CLog::Log(LOGERROR, "COMXAudio::PortSettingsChanged() FAIL audio_splitter OMX_IndexParamAudioInit\n");
      return false;
    }
  }

  //   R E N D E R   I N I T I A L I S A T I O N 

  if(m_config.devices & OMXAudioConfig::AudioDevice::LOCAL)
  {
    if(!m_omx_render_analog.Initialize("OMX.broadcom.audio_render", OMX_IndexParamAudioInit))
    {
      CLog::Log(LOGERROR, "COMXAudio::PortSettingsChanged() FAIL analog audio_render OMX_IndexParamAudioInit\n");
      return false;
    }
  }
  if(m_config.devices & OMXAudioConfig::AudioDevice::HDMI)
  {
    if(!m_omx_render_hdmi.Initialize("OMX.broadcom.audio_render", OMX_IndexParamAudioInit))
    {
      CLog::Log(LOGERROR, "COMXAudio::PortSettingsChanged() FAIL hdmi audio_render OMX_IndexParamAudioInit\n");
      return false;
    }
  }

I'm describing the OpenMAX layer of omxplayer in OMXPlayer-OpenMAX-components.pdf

Work in progress...

fabled commented 7 years ago

I also started working on this a little bit. But I want to pass the full audio device string to OMXAudio so it can create the audio graph from it. This allows e.g. output to two alsa devices, and automatically uses the first audio device as clock master so user can decide that too. I pushed WIP code (does not compile) to https://github.com/fabled/omxplayer/commit/feca5cec671b3e859cde0325f70d18d37d422fa2

fabled commented 7 years ago

Ok, updated code now at https://github.com/fabled/omxplayer/commit/cd09c41960fc9911c2fafe92c74542ecc609477a. It should mostly work, though error checking is not done fully yet.

Use -o local,hdmi for outputting locally (clock master) and hdmi hdmi,alsafor outputting hdmi (clock master) and alsa (default) alsa:mydev,hdmi,local for alsa 'mydev' being clock master but copying to hdmi and local alsa:foo,alsa:bar for two alsa devices etc.

Maybe someone could take a look at the code changes if it looks as an idea ok? I still need to add proper error checking for omx calls and that the maximum amount of audio output devices is not exceeded.

Ruffio commented 7 years ago

@fabled how is it going? It sounds like a lot has been done and only little remains. It would cool to get this implemented

Ruffio commented 7 years ago

@fabled any news on this one?

fabled commented 7 years ago

I'm waiting feedback from @popcornmix for the above mentioned commit.