#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 };
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);
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;
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;
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)
void CAESinkPi::Deinitialize()
{
CLog::Log(LOGDEBUG, "%s:%s", CLASSNAME, __func__);
+ SetAudioProps(false, 0);
if (m_Initialized)
{
m_omx_render.FlushAll();
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();
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()
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)
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);
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);
}