[PiSink] Support planar formats
[vuplus_xbmc] / xbmc / cores / AudioEngine / Sinks / AESinkPi.cpp
index 3242b4b..5a45653 100644 (file)
@@ -34,7 +34,7 @@
 #define CLASSNAME "CAESinkPi"
 
 #define NUM_OMX_BUFFERS 2
-#define AUDIO_PLAYBUFFER (1.0/20.0)
+#define AUDIO_PLAYBUFFER (0.1) // 100ms
 
 static const unsigned int PassthroughSampleRates[] = { 8000, 11025, 16000, 22050, 24000, 32000, 41400, 48000, 88200, 96000, 176400, 192000 };
 
@@ -65,34 +65,133 @@ void CAESinkPi::SetAudioDest()
     CLog::Log(LOGERROR, "%s::%s - m_omx_render.SetConfig omx_err(0x%08x)", CLASSNAME, __func__, omx_err);
 }
 
-bool CAESinkPi::Initialize(AEAudioFormat &format, std::string &device)
+static void SetAudioProps(bool stream_channels, uint32_t channel_map)
 {
-  char response[80];
-  /* if we are raw need to let gpu know */
-  if (AE_IS_RAW(format.m_dataFormat))
+  char command[80], response[80];
+
+  sprintf(command, "hdmi_stream_channels %d", stream_channels ? 1 : 0);
+  vc_gencmd(response, sizeof response, command);
+
+  sprintf(command, "hdmi_channel_map 0x%08x", channel_map);
+  vc_gencmd(response, sizeof response, command);
+
+  CLog::Log(LOGDEBUG, "%s:%s hdmi_stream_channels %d hdmi_channel_map %08x", CLASSNAME, __func__, stream_channels, channel_map);
+}
+
+static uint32_t GetChannelMap(AEAudioFormat &format, bool passthrough)
+{
+  unsigned int channels = format.m_channelLayout.Count();
+  uint32_t channel_map = 0;
+  if (passthrough)
+    return 0;
+
+  static const unsigned char map_normal[] =
   {
-    vc_gencmd(response, sizeof response, "hdmi_stream_channels 1");
-    m_passthrough = true;
-  }
-  else
+    0, //AE_CH_RAW ,
+    1, //AE_CH_FL
+    2, //AE_CH_FR
+    4, //AE_CH_FC
+    3, //AE_CH_LFE
+    7, //AE_CH_BL
+    8, //AE_CH_BR
+    1, //AE_CH_FLOC,
+    2, //AE_CH_FROC,
+    4, //AE_CH_BC,
+    5, //AE_CH_SL
+    6, //AE_CH_SR
+  };
+  static const unsigned char map_back[] =
   {
-    vc_gencmd(response, sizeof response, "hdmi_stream_channels 0");
-    m_passthrough = false;
+    0, //AE_CH_RAW ,
+    1, //AE_CH_FL
+    2, //AE_CH_FR
+    4, //AE_CH_FC
+    3, //AE_CH_LFE
+    5, //AE_CH_BL
+    6, //AE_CH_BR
+    1, //AE_CH_FLOC,
+    2, //AE_CH_FROC,
+    4, //AE_CH_BC,
+    5, //AE_CH_SL
+    6, //AE_CH_SR
+  };
+  const unsigned char *map = map_normal;
+  // According to CEA-861-D only RL and RR are known. In case of a format having SL and SR channels
+  // but no BR BL channels, we use the wide map in order to open only the num of channels really
+  // needed.
+  if (format.m_channelLayout.HasChannel(AE_CH_BL) && !format.m_channelLayout.HasChannel(AE_CH_SL))
+    map = map_back;
+
+  for (unsigned int i = 0; i < channels; ++i)
+  {
+    AEChannel c = format.m_channelLayout[i];
+    unsigned int chan = 0;
+    if ((unsigned int)c < sizeof map_normal / sizeof *map_normal)
+      chan = map[(unsigned int)c];
+    if (chan > 0)
+      channel_map |= (chan-1) << (3*i);
   }
+  // These numbers are from Table 28 Audio InfoFrame Data byte 4 of CEA 861
+  // and describe the speaker layout
+  static const uint8_t cea_map[] = {
+    0xff, // 0
+    0xff, // 1
+    0x00, // 2.0
+    0x02, // 3.0
+    0x08, // 4.0
+    0x0a, // 5.0
+    0xff, // 6
+    0x12, // 7.0
+    0xff, // 8
+  };
+  static const uint8_t cea_map_lfe[] = {
+    0xff, // 0
+    0xff, // 1
+    0xff, // 2
+    0x01, // 2.1
+    0x03, // 3.1
+    0x09, // 4.1
+    0x0b, // 5.1
+    0xff, // 7
+    0x13, // 7.1
+  };
+  uint8_t cea = format.m_channelLayout.HasChannel(AE_CH_LFE) ? cea_map_lfe[channels] : cea_map[channels];
+  if (cea == 0xff)
+    CLog::Log(LOGERROR, "%s::%s - Unexpected CEA mapping %d,%d", CLASSNAME, __func__, format.m_channelLayout.HasChannel(AE_CH_LFE), channels);
+
+  channel_map |= cea << 24;
+
+  return channel_map;
+}
+
+bool CAESinkPi::Initialize(AEAudioFormat &format, std::string &device)
+{
+  // This may be called before Application calls g_RBP.Initialise, so call it here too
+  g_RBP.Initialize();
+
+  /* if we are raw need to let gpu know */
+  m_passthrough = AE_IS_RAW(format.m_dataFormat);
 
   m_initDevice = device;
   m_initFormat = format;
+
+  // analogue only supports stereo
+  if (CSettings::Get().GetString("audiooutput.audiodevice") == "PI:Analogue")
+    format.m_channelLayout = AE_CH_LAYOUT_2_0;
+
   // setup for a 50ms sink feed from SoftAE
-  if (format.m_dataFormat != AE_FMT_FLOAT && format.m_dataFormat != AE_FMT_S32LE)
+  if (format.m_dataFormat != AE_FMT_FLOATP && format.m_dataFormat != AE_FMT_FLOAT &&
+      format.m_dataFormat != AE_FMT_S32NE && format.m_dataFormat != AE_FMT_S32NEP && format.m_dataFormat != AE_FMT_S32LE &&
+      format.m_dataFormat != AE_FMT_S16NE && format.m_dataFormat != AE_FMT_S16NEP && format.m_dataFormat != AE_FMT_S16LE)
     format.m_dataFormat = AE_FMT_S16LE;
   unsigned int channels    = format.m_channelLayout.Count();
   unsigned int sample_size = CAEUtil::DataFormatToBits(format.m_dataFormat) >> 3;
   format.m_frameSize     = sample_size * channels;
   format.m_sampleRate    = std::max(8000U, std::min(192000U, format.m_sampleRate));
-  format.m_frames        = format.m_sampleRate * AUDIO_PLAYBUFFER;
+  format.m_frames        = format.m_sampleRate * AUDIO_PLAYBUFFER / NUM_OMX_BUFFERS;
   format.m_frameSamples  = format.m_frames * channels;
 
-  m_format = format;
+  SetAudioProps(m_passthrough, GetChannelMap(format, m_passthrough));
 
   m_format = format;
   m_sinkbuffer_sec_per_byte = 1.0 / (double)(m_format.m_frameSize * m_format.m_sampleRate);
@@ -100,9 +199,6 @@ bool CAESinkPi::Initialize(AEAudioFormat &format, std::string &device)
   CLog::Log(LOGDEBUG, "%s:%s Format:%d Channels:%d Samplerate:%d framesize:%d bufsize:%d bytes/s=%.2f", CLASSNAME, __func__,
                 m_format.m_dataFormat, channels, m_format.m_sampleRate, m_format.m_frameSize, m_format.m_frameSize * m_format.m_frames, 1.0/m_sinkbuffer_sec_per_byte);
 
-  // This may be called before Application calls g_RBP.Initialise, so call it here too
-  g_RBP.Initialize();
-
   CLog::Log(LOGDEBUG, "%s:%s", CLASSNAME, __func__);
 
   OMX_ERRORTYPE omx_err   = OMX_ErrorNone;
@@ -116,7 +212,13 @@ bool CAESinkPi::Initialize(AEAudioFormat &format, std::string &device)
   m_pcm_input.eEndian               = OMX_EndianLittle;
   m_pcm_input.bInterleaved          = OMX_TRUE;
   m_pcm_input.nBitPerSample         = sample_size * 8;
-  m_pcm_input.ePCMMode              = m_format.m_dataFormat == AE_FMT_FLOAT ? (OMX_AUDIO_PCMMODETYPE)0x8000 : OMX_AUDIO_PCMModeLinear;
+  // 0x8000 = float, 0x10000 = planar
+  uint32_t flags = 0;
+  if (m_format.m_dataFormat == AE_FMT_FLOAT || m_format.m_dataFormat == AE_FMT_FLOATP)
+   flags |= 0x8000;
+  if (AE_IS_PLANAR(m_format.m_dataFormat))
+   flags |= 0x10000;
+  m_pcm_input.ePCMMode              = flags == 0 ? OMX_AUDIO_PCMModeLinear : (OMX_AUDIO_PCMMODETYPE)flags;
   m_pcm_input.nChannels             = channels;
   m_pcm_input.nSamplingRate         = m_format.m_sampleRate;
 
@@ -138,7 +240,7 @@ bool CAESinkPi::Initialize(AEAudioFormat &format, std::string &device)
     CLog::Log(LOGERROR, "%s:%s - error get OMX_IndexParamPortDefinition (input) omx_err(0x%08x)", CLASSNAME, __func__, omx_err);
 
   port_param.nBufferCountActual = std::max((unsigned int)port_param.nBufferCountMin, (unsigned int)NUM_OMX_BUFFERS);
-  port_param.nBufferSize = m_format.m_frameSize * m_format.m_frames / port_param.nBufferCountActual;
+  port_param.nBufferSize = m_format.m_frameSize * m_format.m_frames;
 
   omx_err = m_omx_render.SetParameter(OMX_IndexParamPortDefinition, &port_param);
   if (omx_err != OMX_ErrorNone)
@@ -160,6 +262,7 @@ bool CAESinkPi::Initialize(AEAudioFormat &format, std::string &device)
 void CAESinkPi::Deinitialize()
 {
   CLog::Log(LOGDEBUG, "%s:%s", CLASSNAME, __func__);
+  SetAudioProps(false, 0);
   if (m_Initialized)
   {
     m_omx_render.FlushAll();
@@ -180,13 +283,16 @@ bool CAESinkPi::IsCompatible(const AEAudioFormat &format, const std::string &dev
   return compatible;
 }
 
-double CAESinkPi::GetDelay()
+void CAESinkPi::GetDelay(AEDelayStatus& status)
 {
   OMX_PARAM_U32TYPE param;
   OMX_INIT_STRUCTURE(param);
 
   if (!m_Initialized)
-    return 0.0;
+  {
+    status.SetDelay(0);
+    return;
+  }
 
   param.nPortIndex = m_omx_render.GetInputPort();
 
@@ -198,12 +304,7 @@ double CAESinkPi::GetDelay()
       CLASSNAME, __func__, omx_err);
   }
   double sinkbuffer_seconds_to_empty = m_sinkbuffer_sec_per_byte * param.nU32 * m_format.m_frameSize;
-  return sinkbuffer_seconds_to_empty;
-}
-
-double CAESinkPi::GetCacheTime()
-{
-  return GetDelay();
+  status.SetDelay(sinkbuffer_seconds_to_empty);
 }
 
 double CAESinkPi::GetCacheTotal()
@@ -211,73 +312,64 @@ double CAESinkPi::GetCacheTotal()
   return AUDIO_PLAYBUFFER;
 }
 
-unsigned int CAESinkPi::AddPackets(uint8_t *data, unsigned int frames, bool hasAudio, bool blocking)
+unsigned int CAESinkPi::AddPackets(uint8_t **data, unsigned int frames, unsigned int offset)
 {
-  unsigned int sent = 0;
-
-  if (!m_Initialized)
+  if (!m_Initialized || !frames)
     return frames;
 
   OMX_ERRORTYPE omx_err   = OMX_ErrorNone;
   OMX_BUFFERHEADERTYPE *omx_buffer = NULL;
-  while (sent < frames)
+
+  unsigned int channels    = m_format.m_channelLayout.Count();
+  unsigned int sample_size = CAEUtil::DataFormatToBits(m_format.m_dataFormat) >> 3;
+  const int planes = AE_IS_PLANAR(m_format.m_dataFormat) ? channels : 1;
+  const int chans  = AE_IS_PLANAR(m_format.m_dataFormat) ? 1 : channels;
+  const int pitch  = chans * sample_size;
+
+  AEDelayStatus status;
+  GetDelay(status);
+  double delay = status.GetDelay();
+  if (delay <= 0.0 && m_submitted)
+    CLog::Log(LOGNOTICE, "%s:%s Underrun (delay:%.2f frames:%d)", CLASSNAME, __func__, delay, frames);
+
+  omx_buffer = m_omx_render.GetInputBuffer(1000);
+  if (omx_buffer == NULL)
   {
-    double delay = GetDelay();
-    double ideal_submission_time = AUDIO_PLAYBUFFER - delay;
-    // ideal amount of audio we'd like submit (to make delay match AUDIO_PLAYBUFFER)
-    int timeout = blocking ? 1000 : 0;
-    int ideal_submission_samples = ideal_submission_time / (m_sinkbuffer_sec_per_byte * m_format.m_frameSize);
-    // if we are almost full then sleep (to avoid repeatedly sending a few samples)
-    bool too_laggy = ideal_submission_time < 0.25 * AUDIO_PLAYBUFFER;
-    int sleeptime = (int)(AUDIO_PLAYBUFFER * 0.25 * 1000.0);
-    if (too_laggy)
-    {
-      if (blocking)
-      {
-        Sleep(sleeptime);
-        continue;
-      }
-      break;
-    }
-    omx_buffer = m_omx_render.GetInputBuffer(timeout);
-    if (omx_buffer == NULL)
-    {
-      if (blocking)
-        CLog::Log(LOGERROR, "COMXAudio::Decode timeout");
-      break;
-    }
-
-    unsigned int space = omx_buffer->nAllocLen / m_format.m_frameSize;
-    unsigned int samples = std::min(std::min(space, (unsigned int)ideal_submission_samples), frames - sent);
-
-    omx_buffer->nFilledLen = samples * m_format.m_frameSize;
-    omx_buffer->nTimeStamp = ToOMXTime(0);
-    omx_buffer->nFlags = 0;
-    memcpy(omx_buffer->pBuffer, (uint8_t *)data + sent * m_format.m_frameSize, omx_buffer->nFilledLen);
-
-    sent += samples;
-
-    if (sent == frames)
-      omx_buffer->nFlags |= OMX_BUFFERFLAG_ENDOFFRAME;
-
-    if (delay <= 0.0 && m_submitted)
-      CLog::Log(LOGNOTICE, "%s:%s Underrun (delay:%.2f frames:%d)", CLASSNAME, __func__, delay, frames);
-
-    omx_err = m_omx_render.EmptyThisBuffer(omx_buffer);
-    if (omx_err != OMX_ErrorNone)
-      CLog::Log(LOGERROR, "%s:%s frames=%d err=%x", CLASSNAME, __func__, frames, omx_err);
-    m_submitted++;
+    CLog::Log(LOGERROR, "CAESinkPi::AddPackets timeout");
+    return 0;
   }
 
-  return sent;
+  omx_buffer->nFilledLen = frames * m_format.m_frameSize;
+  // must be true
+  assert(omx_buffer->nFilledLen <= omx_buffer->nAllocLen);
+  omx_buffer->nTimeStamp = ToOMXTime(0);
+  omx_buffer->nFlags = OMX_BUFFERFLAG_ENDOFFRAME;
+
+  if (omx_buffer->nFilledLen)
+  {
+    int planesize = omx_buffer->nFilledLen / planes;
+    for (int i=0; i < planes; i++)
+      memcpy((uint8_t *)omx_buffer->pBuffer + i * planesize, data[i] + offset * pitch, planesize);
+  }
+  omx_err = m_omx_render.EmptyThisBuffer(omx_buffer);
+  if (omx_err != OMX_ErrorNone)
+    CLog::Log(LOGERROR, "%s:%s frames=%d err=%x", CLASSNAME, __func__, frames, omx_err);
+  m_submitted++;
+  GetDelay(status);
+  delay = status.GetDelay();
+  if (delay > AUDIO_PLAYBUFFER)
+    Sleep((int)(1000.0f * (delay - AUDIO_PLAYBUFFER)));
+  return frames;
 }
 
 void CAESinkPi::Drain()
 {
-  int delay = (int)(GetDelay() * 1000.0);
+  AEDelayStatus status;
+  GetDelay(status);
+  int delay = (int)(status.GetDelay() * 1000.0);
   if (delay)
     Sleep(delay);
-  CLog::Log(LOGDEBUG, "%s:%s delay:%dms now:%dms", CLASSNAME, __func__, delay, (int)(GetDelay() * 1000.0));
+  CLog::Log(LOGDEBUG, "%s:%s delay:%dms now:%dms", CLASSNAME, __func__, delay, (int)(status.GetDelay() * 1000.0));
 }
 
 void CAESinkPi::EnumerateDevicesEx(AEDeviceInfoList &list, bool force)
@@ -295,8 +387,13 @@ void CAESinkPi::EnumerateDevicesEx(AEDeviceInfoList &list, bool force)
   for (unsigned int i=0; i<sizeof PassthroughSampleRates/sizeof *PassthroughSampleRates; i++)
     m_info.m_sampleRates.push_back(PassthroughSampleRates[i]);
   m_info.m_dataFormats.push_back(AE_FMT_FLOAT);
+  m_info.m_dataFormats.push_back(AE_FMT_S32NE);
+  m_info.m_dataFormats.push_back(AE_FMT_S16NE);
   m_info.m_dataFormats.push_back(AE_FMT_S32LE);
   m_info.m_dataFormats.push_back(AE_FMT_S16LE);
+  m_info.m_dataFormats.push_back(AE_FMT_FLOATP);
+  m_info.m_dataFormats.push_back(AE_FMT_S32NEP);
+  m_info.m_dataFormats.push_back(AE_FMT_S16NEP);
   m_info.m_dataFormats.push_back(AE_FMT_AC3);
   m_info.m_dataFormats.push_back(AE_FMT_DTS);
   m_info.m_dataFormats.push_back(AE_FMT_EAC3);
@@ -317,6 +414,9 @@ void CAESinkPi::EnumerateDevicesEx(AEDeviceInfoList &list, bool force)
   m_info.m_dataFormats.push_back(AE_FMT_FLOAT);
   m_info.m_dataFormats.push_back(AE_FMT_S32LE);
   m_info.m_dataFormats.push_back(AE_FMT_S16LE);
+  m_info.m_dataFormats.push_back(AE_FMT_FLOATP);
+  m_info.m_dataFormats.push_back(AE_FMT_S32NEP);
+  m_info.m_dataFormats.push_back(AE_FMT_S16NEP);
 
   list.push_back(m_info);
 }