void CAESinkPULSE::GetDelay(AEDelayStatus& status) { if (!m_IsAllocated) { status.SetDelay(0); return; } pa_threaded_mainloop_lock(m_MainLoop); const pa_timing_info* pti = pa_stream_get_timing_info(m_Stream); // only incorporate local sink delay + internal PA transport delay double sink_delay = (pti->configured_sink_usec / 1000000.0); double transport_delay = pti->transport_usec / 1000000.0; uint64_t diff = CurrentHostCounter() - m_lastPackageStamp; unsigned int bytes_played = (unsigned int) ((double) diff * (double) m_BytesPerSecond / (double) CurrentHostFrequency() + 0.5); int buffer_delay = m_filled_bytes - bytes_played; if (buffer_delay < 0) buffer_delay = 0; pa_threaded_mainloop_unlock(m_MainLoop); double delay = buffer_delay / (double) m_BytesPerSecond + sink_delay + transport_delay; status.SetDelay(delay); }
unsigned int CActiveAESink::OutputSamples(CSampleBuffer* samples) { uint8_t **buffer = samples->pkt->data; unsigned int frames = samples->pkt->nb_samples; unsigned int maxFrames; int retry = 0; unsigned int written = 0; switch(m_swapState) { case SKIP_SWAP: break; case NEED_BYTESWAP: Endian_Swap16_buf((uint16_t *)buffer[0], (uint16_t *)buffer[0], frames * samples->pkt->config.channels); break; case CHECK_SWAP: SwapInit(samples); if (m_swapState == NEED_BYTESWAP) Endian_Swap16_buf((uint16_t *)buffer[0], (uint16_t *)buffer[0], frames * samples->pkt->config.channels); break; default: break; } AEDelayStatus status; while(frames > 0) { maxFrames = std::min(frames, m_sinkFormat.m_frames); written = m_sink->AddPackets(buffer, maxFrames, samples->pkt->nb_samples-frames); if (written == 0) { Sleep(500*m_sinkFormat.m_frames/m_sinkFormat.m_sampleRate); retry++; if (retry > 4) { m_extError = true; CLog::Log(LOGERROR, "CActiveAESink::OutputSamples - failed"); status.SetDelay(0); m_stats->UpdateSinkDelay(status, frames); return 0; } else continue; } else if (written > maxFrames) { m_extError = true; CLog::Log(LOGERROR, "CActiveAESink::OutputSamples - sink returned error"); status.SetDelay(0); m_stats->UpdateSinkDelay(status, samples->pool ? maxFrames : 0); return 0; } frames -= written; m_sink->GetDelay(status); m_stats->UpdateSinkDelay(status, samples->pool ? written : 0); } return status.delay; }
void CAESinkPULSE::GetDelay(AEDelayStatus& status) { if (!m_IsAllocated) { status.SetDelay(0); return; } int error = 0; pa_usec_t latency = (pa_usec_t) -1; pa_threaded_mainloop_lock(m_MainLoop); if ((error = pa_stream_get_latency(m_Stream, &latency, NULL)) < 0) { if (error == -PA_ERR_NODATA) { WaitForOperation(pa_stream_update_timing_info(m_Stream, NULL,NULL), m_MainLoop, "Update Timing Information"); if ((error = pa_stream_get_latency(m_Stream, &latency, NULL)) < 0) { CLog::Log(LOGDEBUG, "GetDelay - Failed to get Latency %d", error); } } } if (error < 0 ) latency = (pa_usec_t) 0; pa_threaded_mainloop_unlock(m_MainLoop); status.SetDelay(latency / 1000000.0); }
void CAESinkPi::Drain() { 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)(status.GetDelay() * 1000.0)); }
unsigned int CAESinkPi::AddPackets(uint8_t **data, unsigned int frames, unsigned int offset) { if (!m_Initialized || !m_omx_output || !frames) { Sleep(10); return frames; } OMX_ERRORTYPE omx_err = OMX_ErrorNone; OMX_BUFFERHEADERTYPE *omx_buffer = NULL; 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_output->GetInputBuffer(1000); if (omx_buffer == NULL) { CLog::Log(LOGERROR, "CAESinkPi::AddPackets timeout"); return 0; } 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_output->EmptyThisBuffer(omx_buffer); if (omx_err != OMX_ErrorNone) { CLog::Log(LOGERROR, "%s:%s frames=%d err=%x", CLASSNAME, __func__, frames, omx_err); m_omx_output->DecoderEmptyBufferDone(m_omx_output->GetComponent(), omx_buffer); } m_submitted++; GetDelay(status); delay = status.GetDelay(); if (delay > m_latency) Sleep((int)(1000.0f * (delay - m_latency))); return frames; }
void CAESinkDirectSound::GetDelay(AEDelayStatus& status) { if (!m_initialized) { status.SetDelay(0); return; } /* Make sure we know how much data is in the cache */ if (!UpdateCacheStatus()) m_isDirtyDS = true; /** returns current cached data duration in seconds */ status.SetDelay((double)m_CacheLen / (double)m_AvgBytesPerSec); }
void CAESinkOSS::GetDelay(AEDelayStatus& status) { if (m_fd == -1) { status.SetDelay(0); return; } int delay; if (ioctl(m_fd, SNDCTL_DSP_GETODELAY, &delay) == -1) { status.SetDelay(0); return; } status.SetDelay((double)delay / (m_format.m_frameSize * m_format.m_sampleRate)); }
void CAESinkAUDIOTRACK::GetDelay(AEDelayStatus& status) { if (!m_at_jni) { status.SetDelay(0); return; } // In their infinite wisdom, Google decided to make getPlaybackHeadPosition // return a 32bit "int" that you should "interpret as unsigned." As such, // for wrap saftey, we need to do all ops on it in 32bit integer math. uint32_t head_pos = (uint32_t)m_at_jni->getPlaybackHeadPosition(); double delay = (double)(m_frames_written - head_pos) / m_format.m_sampleRate; status.SetDelay(delay); }
void CAESinkPULSE::GetDelay(AEDelayStatus& status) { if (!m_IsAllocated) { status.SetDelay(0); return; } pa_threaded_mainloop_lock(m_MainLoop); pa_usec_t r_usec; int negative; if (pa_stream_get_latency(m_Stream, &r_usec, &negative) < 0) r_usec = 0; pa_threaded_mainloop_unlock(m_MainLoop); status.SetDelay(r_usec / 1000000.0); }
void CAESinkPi::GetDelay(AEDelayStatus& status) { OMX_PARAM_U32TYPE param; OMX_INIT_STRUCTURE(param); if (!m_Initialized) { status.SetDelay(0); return; } param.nPortIndex = m_omx_render.GetInputPort(); OMX_ERRORTYPE omx_err = m_omx_render.GetConfig(OMX_IndexConfigAudioRenderingLatency, ¶m); if (omx_err != OMX_ErrorNone) { CLog::Log(LOGERROR, "%s::%s - error getting OMX_IndexConfigAudioRenderingLatency error 0x%08x", CLASSNAME, __func__, omx_err); } double sinkbuffer_seconds_to_empty = m_sinkbuffer_sec_per_byte * param.nU32 * m_format.m_frameSize; status.SetDelay(sinkbuffer_seconds_to_empty); }
void CAESinkSNDIO::GetDelay(AEDelayStatus& status) { unsigned int frameSize = m_par.bps * m_par.pchan; double delay = 1.0 * ((m_written / frameSize) - m_played) / m_par.rate; status.SetDelay(delay); }
double CActiveAEStream::GetDelay() { AEDelayStatus status; AE.GetDelay(status, this); return status.GetDelay(); }
unsigned int CActiveAESink::OutputSamples(CSampleBuffer* samples) { uint8_t **buffer = samples->pkt->data; uint8_t *packBuffer; unsigned int frames = samples->pkt->nb_samples; unsigned int totalFrames = frames; unsigned int maxFrames; int retry = 0; unsigned int written = 0; std::unique_ptr<uint8_t[]> mergebuffer; uint8_t* p_mergebuffer = NULL; AEDelayStatus status; if (m_requestedFormat.m_dataFormat == AE_FMT_RAW) { if (m_requestedFormat.m_streamInfo.m_IECPacked) { if (frames > 0) { m_packer->Reset(); if (m_sinkFormat.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_TRUEHD) { if (frames == 61440) { int offset; int len; m_packer->GetBuffer(); for (int i=0; i<24; i++) { offset = i*2560; len = (*(buffer[0] + offset+2560-2) << 8) + *(buffer[0] + offset+2560-1); m_packer->Pack(m_sinkFormat.m_streamInfo, buffer[0] + offset, len); } } else { m_extError = true; CLog::Log(LOGERROR, "CActiveAESink::OutputSamples - incomplete TrueHD buffer"); return 0; } } else m_packer->Pack(m_sinkFormat.m_streamInfo, buffer[0], frames); } else if (samples->pkt->pause_burst_ms > 0) { // construct a pause burst m_packer->PackPause(m_sinkFormat.m_streamInfo, samples->pkt->pause_burst_ms); } else m_packer->Reset(); unsigned int size = m_packer->GetSize(); packBuffer = m_packer->GetBuffer(); buffer = &packBuffer; totalFrames = size / m_sinkFormat.m_frameSize; frames = totalFrames; switch(m_swapState) { case SKIP_SWAP: break; case NEED_BYTESWAP: Endian_Swap16_buf((uint16_t *)buffer[0], (uint16_t *)buffer[0], size / 2); break; case CHECK_SWAP: SwapInit(samples); if (m_swapState == NEED_BYTESWAP) Endian_Swap16_buf((uint16_t *)buffer[0], (uint16_t *)buffer[0], size / 2); break; default: break; } } else { if (m_sinkFormat.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_TRUEHD && frames == 61440) { int offset; int len; unsigned int size = 0; mergebuffer.reset(new uint8_t[MAX_IEC61937_PACKET]); p_mergebuffer = mergebuffer.get(); for (int i=0; i<24; i++) { offset = i*2560; len = (*(buffer[0] + offset+2560-2) << 8) + *(buffer[0] + offset+2560-1); memcpy(&(mergebuffer.get())[size], buffer[0] + offset, len); size += len; } buffer = &p_mergebuffer; totalFrames = size / m_sinkFormat.m_frameSize; frames = totalFrames; } if (samples->pkt->pause_burst_ms > 0) { m_sink->AddPause(samples->pkt->pause_burst_ms); m_sink->GetDelay(status); m_stats->UpdateSinkDelay(status, samples->pool ? 1 : 0); return status.delay * 1000; } } } int framesOrPackets; while (frames > 0) { maxFrames = std::min(frames, m_sinkFormat.m_frames); written = m_sink->AddPackets(buffer, maxFrames, totalFrames - frames); if (written == 0) { Sleep(500*m_sinkFormat.m_frames/m_sinkFormat.m_sampleRate); retry++; if (retry > 4) { m_extError = true; CLog::Log(LOGERROR, "CActiveAESink::OutputSamples - failed"); status.SetDelay(0); framesOrPackets = frames; if (m_requestedFormat.m_dataFormat == AE_FMT_RAW) framesOrPackets = 1; m_stats->UpdateSinkDelay(status, samples->pool ? framesOrPackets : 0); return 0; } else continue; } else if (written > maxFrames) { m_extError = true; CLog::Log(LOGERROR, "CActiveAESink::OutputSamples - sink returned error"); status.SetDelay(0); framesOrPackets = frames; if (m_requestedFormat.m_dataFormat == AE_FMT_RAW) framesOrPackets = 1; m_stats->UpdateSinkDelay(status, samples->pool ? framesOrPackets : 0); return 0; } frames -= written; m_sink->GetDelay(status); if (m_requestedFormat.m_dataFormat != AE_FMT_RAW) m_stats->UpdateSinkDelay(status, samples->pool ? written : 0); } if (m_requestedFormat.m_dataFormat == AE_FMT_RAW) m_stats->UpdateSinkDelay(status, samples->pool ? 1 : 0); return status.delay * 1000; }
void CAESinkAUDIOTRACK::GetDelay(AEDelayStatus& status) { if (!m_at_jni) { status.SetDelay(0); return; } // In their infinite wisdom, Google decided to make getPlaybackHeadPosition // return a 32bit "int" that you should "interpret as unsigned." As such, // for wrap safety, we need to do all ops on it in 32bit integer math. uint32_t head_pos = (uint32_t)m_at_jni->getPlaybackHeadPosition(); // Wraparound if ((uint32_t)(m_headPos & UINT64_LOWER_BYTES) > head_pos) // need to compute wraparound m_headPos += (1ULL << 32); // add wraparound, e.g. 0x0000 FFFF FFFF -> 0x0001 FFFF FFFF // clear lower 32 bit values, e.g. 0x0001 FFFF FFFF -> 0x0001 0000 0000 // and add head_pos which wrapped around, e.g. 0x0001 0000 0000 -> 0x0001 0000 0004 m_headPos = (m_headPos & UINT64_UPPER_BYTES) | (uint64_t)head_pos; // head_pos does not necessarily start at the beginning if (m_offset == -1 && m_at_jni->getPlayState() == CJNIAudioTrack::PLAYSTATE_PLAYING) { m_offset = m_headPos; } if (m_offset != -1 && (uint64_t) m_offset > m_headPos) { CLog::Log(LOGDEBUG, "You did it wrong man - fully wrong! offset %lld head pos %llu", m_offset, m_headPos); m_offset = 0; } // we might not yet be running here, but we need m_offset to track first PT fillup uint64_t normHead_pos = m_headPos; if (m_offset > 0) m_headPos -= m_offset; // this makes EAC3 working even when AML is not enabled if (aml_present() && m_info.m_wantsIECPassthrough && (m_encoding == CJNIAudioFormat::ENCODING_DTS_HD || m_encoding == CJNIAudioFormat::ENCODING_E_AC3 || m_encoding == CJNIAudioFormat::ENCODING_DOLBY_TRUEHD)) normHead_pos /= m_sink_frameSize; // AML wants sink in 48k but returns pos in 192k if (m_passthrough && !m_info.m_wantsIECPassthrough) { if (m_extTimer.MillisLeft() > 0) { const double d = GetMovingAverageDelay(GetCacheTotal()); status.SetDelay(d); return; } } double gone = (double) normHead_pos / (double) m_sink_sampleRate; // if sink is run dry without buffer time written anymore if (gone > m_duration_written) gone = m_duration_written; double delay = m_duration_written - gone; if (delay < 0) delay = 0; const double d = GetMovingAverageDelay(delay); status.SetDelay(d); }
void CAESinkAUDIOTRACK::GetDelay(AEDelayStatus& status) { if (!m_at_jni) { status.SetDelay(0); return; } // In their infinite wisdom, Google decided to make getPlaybackHeadPosition // return a 32bit "int" that you should "interpret as unsigned." As such, // for wrap saftey, we need to do all ops on it in 32bit integer math. uint32_t head_pos = (uint32_t)m_at_jni->getPlaybackHeadPosition(); // head_pos does not necessarily start at the beginning if (m_offset == -1 && m_at_jni->getPlayState() == CJNIAudioTrack::PLAYSTATE_PLAYING) { CLog::Log(LOGDEBUG, "Offset updated to %u", head_pos); m_offset = head_pos; } if (m_offset > head_pos) { CLog::Log(LOGDEBUG, "You did it wrong man - fully wrong! offset %lld head pos %u", m_offset, head_pos); m_offset = 0; } uint32_t normHead_pos = head_pos - m_offset; if (m_passthrough && !m_info.m_wantsIECPassthrough) { if (m_pause_time > 0) { const double d = GetMovingAverageDelay(GetCacheTotal()); CLog::Log(LOGDEBUG, "Faking Delay: smooth %lf measured: %lf", d * 1000, GetCacheTotal() * 1000); status.SetDelay(d); return; } } if (normHead_pos > m_lastPlaybackHeadPosition) { unsigned int differencehead = normHead_pos - m_lastPlaybackHeadPosition; CLog::Log(LOGDEBUG, "Sink advanced: %u", differencehead); m_lastPlaybackHeadPosition = normHead_pos; } double gone = (double) normHead_pos / (double) m_sink_sampleRate; // if sink is run dry without buffer time written anymore if (gone > m_duration_written) gone = m_duration_written; double delay = m_duration_written - gone; if (delay < 0) delay = 0; const double d = GetMovingAverageDelay(delay); CLog::Log(LOGDEBUG, "Calculations duration written: %lf sampleRate: %u gone: %lf", m_duration_written, m_sink_sampleRate, gone); bool playing = m_at_jni->getPlayState() == CJNIAudioTrack::PLAYSTATE_PLAYING; CLog::Log(LOGDEBUG, "Current-Delay: smoothed: %lf measured: %lf Head Pos: %u Playing: %s", d * 1000, delay * 1000, normHead_pos, playing ? "yes" : "no"); status.SetDelay(d); }
void CAESinkAUDIOTRACK::GetDelay(AEDelayStatus& status) { if (!m_at_jni) { status.SetDelay(0); return; } // In their infinite wisdom, Google decided to make getPlaybackHeadPosition // return a 32bit "int" that you should "interpret as unsigned." As such, // for wrap saftey, we need to do all ops on it in 32bit integer math. uint32_t head_pos = (uint32_t)m_at_jni->getPlaybackHeadPosition(); // head_pos does not necessarily start at the beginning if (m_offset == -1 && m_at_jni->getPlayState() == CJNIAudioTrack::PLAYSTATE_PLAYING) { m_offset = head_pos; } if (m_offset > head_pos) { CLog::Log(LOGDEBUG, "You did it wrong man - fully wrong! offset %lld head pos %u", m_offset, head_pos); m_offset = 0; } uint32_t normHead_pos = head_pos - m_offset; #if defined(HAS_LIBAMCODEC) if (aml_present() && (m_encoding == CJNIAudioFormat::ENCODING_DTS_HD || m_encoding == CJNIAudioFormat::ENCODING_E_AC3 || m_encoding == CJNIAudioFormat::ENCODING_DOLBY_TRUEHD)) normHead_pos /= m_sink_frameSize; // AML wants sink in 48k but returns pos in 192k #endif if (m_passthrough && !m_info.m_wantsIECPassthrough) { if (m_extTimer.MillisLeft() > 0) { const double d = GetMovingAverageDelay(GetCacheTotal()); status.SetDelay(d); return; } } if (normHead_pos > m_lastPlaybackHeadPosition) { unsigned int differencehead = normHead_pos - m_lastPlaybackHeadPosition; m_lastPlaybackHeadPosition = normHead_pos; } double gone = (double) normHead_pos / (double) m_sink_sampleRate; // if sink is run dry without buffer time written anymore if (gone > m_duration_written) gone = m_duration_written; double delay = m_duration_written - gone; if (delay < 0) delay = 0; const double d = GetMovingAverageDelay(delay); status.SetDelay(d); }
void CAESinkIntelSMD::GetDelay(AEDelayStatus& status) { status.SetDelay(GetCacheTime()); }