#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 };
CAEDeviceInfo CAESinkPi::m_info;
CAESinkPi::CAESinkPi() :
- m_sinkbuffer_size(0),
m_sinkbuffer_sec_per_byte(0),
m_Initialized(false),
m_submitted(0)
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
- format.m_dataFormat = AE_FMT_S16NE;
- format.m_frames = format.m_sampleRate * AUDIO_PLAYBUFFER;
- format.m_frameSamples = format.m_channelLayout.Count();
- format.m_frameSize = format.m_frameSamples * (CAEUtil::DataFormatToBits(format.m_dataFormat) >> 3);
- format.m_sampleRate = std::max(8000U, std::min(96000U, format.m_sampleRate));
+ 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 / NUM_OMX_BUFFERS;
+ format.m_frameSamples = format.m_frames * channels;
+
+ SetAudioProps(m_passthrough, GetChannelMap(format, m_passthrough));
m_format = format;
-
- m_sinkbuffer_size = format.m_frameSize * format.m_frames * NUM_OMX_BUFFERS;
- m_sinkbuffer_sec_per_byte = 1.0 / (double)(format.m_frameSize * format.m_sampleRate);
+ 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__,
- format.m_dataFormat, format.m_channelLayout.Count(), format.m_sampleRate, format.m_frameSize, m_sinkbuffer_size, 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();
+ 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);
CLog::Log(LOGDEBUG, "%s:%s", CLASSNAME, __func__);
m_pcm_input.eNumData = OMX_NumericalDataSigned;
m_pcm_input.eEndian = OMX_EndianLittle;
m_pcm_input.bInterleaved = OMX_TRUE;
- m_pcm_input.nBitPerSample = 16;
- m_pcm_input.ePCMMode = OMX_AUDIO_PCMModeLinear;
- m_pcm_input.nChannels = m_format.m_frameSamples;
+ m_pcm_input.nBitPerSample = sample_size * 8;
+ // 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;
- m_pcm_input.eChannelMapping[0] = OMX_AUDIO_ChannelLF;
- m_pcm_input.eChannelMapping[1] = OMX_AUDIO_ChannelRF;
- m_pcm_input.eChannelMapping[2] = OMX_AUDIO_ChannelMax;
omx_err = m_omx_render.SetParameter(OMX_IndexParamAudioPcm, &m_pcm_input);
if (omx_err != OMX_ErrorNone)
- CLog::Log(LOGERROR, "%s::%s - error m_omx_render SetParameter omx_err(0x%08x)", CLASSNAME, __func__, omx_err);
+ CLog::Log(LOGERROR, "%s::%s - error m_omx_render SetParameter in omx_err(0x%08x)", CLASSNAME, __func__, omx_err);
m_omx_render.ResetEos();
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_sinkbuffer_size / 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()
{
- double audioplus_buffer = AUDIO_PLAYBUFFER;
- return m_sinkbuffer_sec_per_byte * (double)m_sinkbuffer_size + audioplus_buffer;
+ 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)
{
- int timeout = blocking ? 1000 : 0;
-
- // delay compared to maximum we'd like (to keep lag low)
- double delay = GetDelay();
- bool too_laggy = delay - AUDIO_PLAYBUFFER > 0.0;
- omx_buffer = too_laggy ? NULL : m_omx_render.GetInputBuffer(timeout);
-
- if (omx_buffer == NULL)
- {
- if (too_laggy)
- {
- Sleep((int)((delay - AUDIO_PLAYBUFFER) * 1000.0));
- continue;
- }
- if (blocking)
- CLog::Log(LOGERROR, "COMXAudio::Decode timeout");
- break;
- }
-
- omx_buffer->nFilledLen = std::min(omx_buffer->nAllocLen, (frames - sent) * 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 += omx_buffer->nFilledLen / m_format.m_frameSize;
-
- if (sent == frames)
- omx_buffer->nFlags |= OMX_BUFFERFLAG_ENDOFFRAME;
-
- if (delay <= 0.0 && m_submitted)
- CLog::Log(LOGERROR, "%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 += omx_buffer->nFilledLen;
+ 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)
m_info.m_channels += AE_CH_FR;
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_channels += AE_CH_FL;
m_info.m_channels += AE_CH_FR;
m_info.m_sampleRates.push_back(48000);
+ 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);
}