Skip to content

Commit

Permalink
ADD: [droid] audio passthrough via API
Browse files Browse the repository at this point in the history
  • Loading branch information
koying committed Nov 1, 2015
1 parent 8e16f6c commit cfe01c0
Show file tree
Hide file tree
Showing 9 changed files with 220 additions and 47 deletions.
24 changes: 19 additions & 5 deletions xbmc/android/jni/AudioFormat.cpp
Expand Up @@ -25,6 +25,11 @@
using namespace jni;

int CJNIAudioFormat::ENCODING_PCM_16BIT = 0x00000002;
int CJNIAudioFormat::ENCODING_AC3 = 0x00000005;
int CJNIAudioFormat::ENCODING_E_AC3 = 0x00000006;
int CJNIAudioFormat::ENCODING_DTS = 0x00000007;
int CJNIAudioFormat::ENCODING_DTS_HD = 0x00000008;
int CJNIAudioFormat::ENCODING_DOLBY_TRUEHD = 0x00000009;

int CJNIAudioFormat::CHANNEL_OUT_STEREO = 0x0000000c;
int CJNIAudioFormat::CHANNEL_OUT_5POINT1 = 0x000000fc;
Expand Down Expand Up @@ -64,11 +69,20 @@ void CJNIAudioFormat::PopulateStaticFields()
CJNIAudioFormat::CHANNEL_OUT_BACK_CENTER = get_static_field<int>(c, "CHANNEL_OUT_BACK_CENTER");
CJNIAudioFormat::CHANNEL_OUT_BACK_RIGHT = get_static_field<int>(c, "CHANNEL_OUT_BACK_RIGHT");
CJNIAudioFormat::CHANNEL_INVALID = get_static_field<int>(c, "CHANNEL_INVALID");
if (sdk >= 21)
{
CJNIAudioFormat::CHANNEL_OUT_SIDE_LEFT = get_static_field<int>(c, "CHANNEL_OUT_SIDE_LEFT");
CJNIAudioFormat::CHANNEL_OUT_SIDE_RIGHT = get_static_field<int>(c, "CHANNEL_OUT_SIDE_RIGHT");
}
}
if (sdk >= 21)
{
CJNIAudioFormat::CHANNEL_OUT_SIDE_LEFT = get_static_field<int>(c, "CHANNEL_OUT_SIDE_LEFT");
CJNIAudioFormat::CHANNEL_OUT_SIDE_RIGHT = get_static_field<int>(c, "CHANNEL_OUT_SIDE_RIGHT");

CJNIAudioFormat::ENCODING_AC3 = get_static_field<int>(c, "ENCODING_AC3");
CJNIAudioFormat::ENCODING_E_AC3 = get_static_field<int>(c, "ENCODING_E_AC3");
}
if (sdk >= 23)
{
CJNIAudioFormat::ENCODING_DTS = get_static_field<int>(c, "ENCODING_DTS");
CJNIAudioFormat::ENCODING_DTS_HD = get_static_field<int>(c, "ENCODING_DTS_HD");
//CJNIAudioFormat::ENCODING_DOLBY_TRUEHD = get_static_field<int>(c, "ENCODING_DOLBY_TRUEHD");
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions xbmc/android/jni/AudioFormat.h
Expand Up @@ -28,6 +28,11 @@ class CJNIAudioFormat
static void PopulateStaticFields();

static int ENCODING_PCM_16BIT;
static int ENCODING_AC3;
static int ENCODING_E_AC3;
static int ENCODING_DTS;
static int ENCODING_DTS_HD;
static int ENCODING_DOLBY_TRUEHD;

static int CHANNEL_OUT_STEREO;
static int CHANNEL_OUT_5POINT1;
Expand Down
4 changes: 4 additions & 0 deletions xbmc/android/jni/AudioTrack.cpp
Expand Up @@ -25,13 +25,17 @@ using namespace jni;

int CJNIAudioTrack::MODE_STREAM = 0x00000001;
int CJNIAudioTrack::PLAYSTATE_PLAYING = 0x00000003;
int CJNIAudioTrack::PLAYSTATE_STOPPED = 0x00000001;
int CJNIAudioTrack::PLAYSTATE_PAUSED = 0x00000002;

void CJNIAudioTrack::PopulateStaticFields()
{
if (CJNIBase::GetSDKVersion() >= 3)
{
jhclass c = find_class("android/media/AudioTrack");
CJNIAudioTrack::PLAYSTATE_PLAYING = get_static_field<int>(c, "PLAYSTATE_PLAYING");
CJNIAudioTrack::PLAYSTATE_STOPPED = get_static_field<int>(c, "PLAYSTATE_STOPPED");
CJNIAudioTrack::PLAYSTATE_PAUSED = get_static_field<int>(c, "PLAYSTATE_PAUSED");
if (CJNIBase::GetSDKVersion() >= 5)
CJNIAudioTrack::MODE_STREAM = get_static_field<int>(c, "MODE_STREAM");
}
Expand Down
4 changes: 3 additions & 1 deletion xbmc/android/jni/AudioTrack.h
Expand Up @@ -44,7 +44,9 @@ class CJNIAudioTrack : public CJNIBase

static int MODE_STREAM;
static int PLAYSTATE_PLAYING;

static int PLAYSTATE_STOPPED;
static int PLAYSTATE_PAUSED;

static void PopulateStaticFields();
static int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat);
static int getNativeOutputSampleRate(int streamType);
Expand Down
199 changes: 170 additions & 29 deletions xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp
Expand Up @@ -21,12 +21,14 @@
#include "AESinkAUDIOTRACK.h"
#include "cores/AudioEngine/Utils/AEUtil.h"
#include "cores/AudioEngine/Utils/AERingBuffer.h"
#include "cores/AudioEngine/Utils/AEPackIEC61937.h"
#include "android/activity/XBMCApp.h"
#include "settings/Settings.h"
#if defined(HAS_LIBAMCODEC)
#include "utils/AMLUtils.h"
#endif
#include "utils/log.h"
#include "utils/StringUtils.h"

#include "android/jni/AudioFormat.h"
#include "android/jni/AudioManager.h"
Expand Down Expand Up @@ -161,16 +163,16 @@ static int AEChannelMapToAUDIOTRACKChannelMask(CAEChannelInfo info)
return atMask;
}

static jni::CJNIAudioTrack *CreateAudioTrack(int sampleRate, int channelMask, int bufferSize)
static jni::CJNIAudioTrack *CreateAudioTrack(int stream, int sampleRate, int channelMask, int encoding, int bufferSize)
{
jni::CJNIAudioTrack *jniAt = NULL;

try
{
jniAt = new CJNIAudioTrack(CJNIAudioManager::STREAM_MUSIC,
jniAt = new CJNIAudioTrack(stream,
sampleRate,
channelMask,
CJNIAudioFormat::ENCODING_PCM_16BIT,
encoding,
bufferSize,
CJNIAudioTrack::MODE_STREAM);
}
Expand All @@ -188,11 +190,12 @@ CAEDeviceInfo CAESinkAUDIOTRACK::m_info;
CAESinkAUDIOTRACK::CAESinkAUDIOTRACK()
{
m_alignedS16 = NULL;
m_min_frames = 0;
m_sink_frameSize = 0;
m_audiotrackbuffer_sec = 0.0;
m_at_jni = NULL;
m_frames_written = 0;
m_lastHeadPosition = 0;
m_ptOffset = 0;
}

CAESinkAUDIOTRACK::~CAESinkAUDIOTRACK()
Expand All @@ -210,39 +213,102 @@ bool CAESinkAUDIOTRACK::Initialize(AEAudioFormat &format, std::string &device)
{
m_format = format;
m_volume = -1;
m_ptBuffer = NULL;
m_silenceframes = 0;

int stream = CJNIAudioManager::STREAM_MUSIC;
m_encoding = CJNIAudioFormat::ENCODING_PCM_16BIT;

m_sink_sampleRate = CJNIAudioTrack::getNativeOutputSampleRate(CJNIAudioManager::STREAM_MUSIC);
for (size_t i = 0; i < m_info.m_sampleRates.size(); i++)
{
if (m_format.m_sampleRate == m_info.m_sampleRates[i])
{
m_sink_sampleRate = m_format.m_sampleRate;
break;
}
}
m_format.m_sampleRate = m_sink_sampleRate;

if (AE_IS_RAW(m_format.m_dataFormat))
{
m_passthrough = true;
if (!WantsIEC61937())
{
switch (m_format.m_dataFormat)
{
case AE_FMT_AC3:
m_encoding = CJNIAudioFormat::ENCODING_AC3;
m_format.m_frames = AC3_FRAME_SIZE;
break;

case AE_FMT_EAC3:
m_encoding = CJNIAudioFormat::ENCODING_E_AC3;
m_sink_sampleRate = m_format.m_sampleRate / 4;
m_format.m_frames = EAC3_FRAME_SIZE;
break;

case AE_FMT_DTS:
m_encoding = CJNIAudioFormat::ENCODING_DTS;
m_format.m_frames = DTS1_FRAME_SIZE;
break;

case AE_FMT_DTSHD:
m_encoding = CJNIAudioFormat::ENCODING_DTS_HD;
break;

case AE_FMT_TRUEHD:
m_encoding = CJNIAudioFormat::ENCODING_DOLBY_TRUEHD;
break;

default:
break;
}
}
else
m_format.m_dataFormat = AE_FMT_S16LE;
}
else
{
m_passthrough = false;
m_format.m_dataFormat = AE_FMT_S16LE;
}

int atChannelMask = AEChannelMapToAUDIOTRACKChannelMask(m_format.m_channelLayout);
m_format.m_channelLayout = AUDIOTRACKChannelMaskToAEChannelMap(atChannelMask);
m_format.m_frameSize = m_format.m_channelLayout.Count() *
(CAEUtil::DataFormatToBits(m_format.m_dataFormat) / 8);
m_sink_frameSize = m_format.m_frameSize;

#if defined(HAS_LIBAMCODEC)
if (CSettings::GetInstance().GetBool(CSettings::SETTING_VIDEOPLAYER_USEAMCODEC))
aml_set_audio_passthrough(m_passthrough);
#endif

int atChannelMask = AEChannelMapToAUDIOTRACKChannelMask(m_format.m_channelLayout);

m_format.m_sampleRate = CJNIAudioTrack::getNativeOutputSampleRate(CJNIAudioManager::STREAM_MUSIC);
m_format.m_dataFormat = AE_FMT_S16LE;

while (!m_at_jni)
{
m_format.m_channelLayout = AUDIOTRACKChannelMaskToAEChannelMap(atChannelMask);
m_format.m_frameSize = m_format.m_channelLayout.Count() *
(CAEUtil::DataFormatToBits(m_format.m_dataFormat) / 8);
int min_buffer_size = CJNIAudioTrack::getMinBufferSize( m_format.m_sampleRate,
unsigned int min_buffer_size = CJNIAudioTrack::getMinBufferSize( m_sink_sampleRate,
atChannelMask,
CJNIAudioFormat::ENCODING_PCM_16BIT);
m_sink_frameSize = m_format.m_channelLayout.Count() *
(CAEUtil::DataFormatToBits(AE_FMT_S16LE) / 8);
m_min_frames = min_buffer_size / m_sink_frameSize;
m_audiotrackbuffer_sec = (double)m_min_frames / (double)m_format.m_sampleRate;

m_at_jni = CreateAudioTrack(m_format.m_sampleRate,
atChannelMask,
m_encoding);
if (m_passthrough && !WantsIEC61937())
{
min_buffer_size = (min_buffer_size * 4 / (m_format.m_frameSize*m_format.m_frames)+1) * (m_format.m_frameSize*m_format.m_frames);
m_ptBuffer = (uint8_t*)malloc(m_sink_frameSize * m_format.m_frames);
}
else
{
m_format.m_frames = (int)(min_buffer_size / m_sink_frameSize) / 2;
}

m_format.m_frameSamples = m_format.m_frames * m_format.m_channelLayout.Count();
m_audiotrackbuffer_sec = (double)(min_buffer_size / m_sink_frameSize) / (double)m_format.m_sampleRate;

m_at_jni = CreateAudioTrack(stream, m_sink_sampleRate,
atChannelMask, m_encoding,
min_buffer_size);

CLog::Log(LOGDEBUG, "CAESinkAUDIOTRACK::Initialize m_sampleRate %u; min_buffer_size %u; m_frames %u; m_frameSize %u", m_format.m_sampleRate, min_buffer_size, m_format.m_frames, m_format.m_frameSize);

if (!m_at_jni)
{
if (atChannelMask != CJNIAudioFormat::CHANNEL_OUT_STEREO &&
Expand All @@ -264,9 +330,6 @@ bool CAESinkAUDIOTRACK::Initialize(AEAudioFormat &format, std::string &device)
}
}

m_format.m_frames = m_min_frames / 2;

m_format.m_frameSamples = m_format.m_frames * m_format.m_channelLayout.Count();
format = m_format;

// Force volume to 100% for passthrough
Expand Down Expand Up @@ -297,6 +360,10 @@ void CAESinkAUDIOTRACK::Deinitialize()
m_at_jni->release();

m_frames_written = 0;
m_lastHeadPosition = 0;
m_ptOffset = 0;
if (m_ptBuffer)
SAFE_DELETE(m_ptBuffer);

delete m_at_jni;
m_at_jni = NULL;
Expand All @@ -315,7 +382,20 @@ void CAESinkAUDIOTRACK::GetDelay(AEDelayStatus& status)
// 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;
double delay;
if (m_passthrough && !WantsIEC61937())
{
if (!head_pos && m_at_jni->getPlayState() == CJNIAudioTrack::PLAYSTATE_PAUSED)
m_ptOffset = m_lastHeadPosition;

head_pos += m_ptOffset;
m_lastHeadPosition = head_pos;

delay = ((double)(m_frames_written - m_silenceframes) / m_format.m_sampleRate) - ((double)head_pos / m_sink_sampleRate);
CLog::Log(LOGDEBUG, "CAESinkAUDIOTRACK::GetDelay m_frames_written/head_pos %u/%u %f", m_frames_written, head_pos, delay);
}
else
delay = (double)(m_frames_written - head_pos) / m_sink_sampleRate;

status.SetDelay(delay);
}
Expand All @@ -339,6 +419,51 @@ unsigned int CAESinkAUDIOTRACK::AddPackets(uint8_t **data, unsigned int frames,
return INT_MAX;

uint8_t *buffer = data[0]+offset*m_format.m_frameSize;
uint8_t *out_buf = buffer;
int size = frames * m_format.m_frameSize;

if (m_passthrough && !WantsIEC61937())
{
#if 0
CLog::Log(LOGDEBUG, "CAESinkAUDIOTRACK::AddPackets size: %d", size);
if (size)
{
std::string line;
for (unsigned int y=0; y*8 < size && y*8 < 16; ++y)
{
line = "";
for (unsigned int x=0; x<8 && y*8 + x < size && y*8 + x < 16; ++x)
{
line += StringUtils::Format("%02x ", ((char *)buffer)[y*8+x]);
}
CLog::Log(LOGDEBUG, "%s", line.c_str());
}
}
#endif

CAEPackIEC61937::IEC61937Packet *packet;
packet = (CAEPackIEC61937::IEC61937Packet*)buffer;
if (packet->m_preamble1 == IEC61937_PREAMBLE1 && packet->m_preamble2 == IEC61937_PREAMBLE2)
{
int psize = packet->m_length;
if ((packet->m_type & 0x3f) < 0x10)
psize = psize >> 3;
buffer = packet->m_data;
#ifndef __BIG_ENDIAN__
out_buf = m_ptBuffer;
CAEPackIEC61937::SwapEndian((uint16_t*)out_buf, (uint16_t*)buffer, psize >> 1);
memset(out_buf + psize, 0, size - psize);
#else
out_buf = buffer;
#endif
CLog::Log(LOGERROR, "CAESinkAUDIOTRACK::AddPackets AC3: found packet %d/%d", psize, size);
}
else
{
CLog::Log(LOGERROR, "CAESinkAUDIOTRACK::AddPackets AC3: not a valid IEC61937 packet %d", size);
m_silenceframes += frames;
}
}

// write as many frames of audio as we can fit into our internal buffer.
int written = 0;
Expand All @@ -349,12 +474,13 @@ unsigned int CAESinkAUDIOTRACK::AddPackets(uint8_t **data, unsigned int frames,
// writing into its buffer.
if (m_at_jni->getPlayState() != CJNIAudioTrack::PLAYSTATE_PLAYING)
m_at_jni->play();

written = m_at_jni->write((char*)buffer, 0, frames * m_sink_frameSize);
written = m_at_jni->write((char*)out_buf, 0, size);
m_frames_written += written / m_sink_frameSize;
}

return (unsigned int)(written/m_sink_frameSize);
//CLog::Log(LOGDEBUG, "CAESinkAUDIOTRACK::AddPackets written %d", written);

return (unsigned int)(written/m_format.m_frameSize);
}

void CAESinkAUDIOTRACK::Drain()
Expand All @@ -368,6 +494,14 @@ void CAESinkAUDIOTRACK::Drain()
m_frames_written = 0;
}

bool CAESinkAUDIOTRACK::WantsIEC61937()
{
if (CJNIAudioManager::GetSDKVersion() >= 21)
return false;

return true;
}

void CAESinkAUDIOTRACK::EnumerateDevicesEx(AEDeviceInfoList &list, bool force)
{
m_info.m_channels.Reset();
Expand All @@ -392,7 +526,7 @@ void CAESinkAUDIOTRACK::EnumerateDevicesEx(AEDeviceInfoList &list, bool force)
if (!CXBMCApp::IsHeadsetPlugged())
{
m_info.m_deviceType = AE_DEVTYPE_HDMI;
int test_sample[] = { 44100, 48000, 96000, 192000 };
int test_sample[] = { 32000, 44100, 48000, 96000, 192000 };
int test_sample_sz = sizeof(test_sample) / sizeof(int);
for (int i=0; i<test_sample_sz; ++i)
{
Expand All @@ -404,6 +538,13 @@ void CAESinkAUDIOTRACK::EnumerateDevicesEx(AEDeviceInfoList &list, bool force)
}
m_info.m_dataFormats.push_back(AE_FMT_AC3);
m_info.m_dataFormats.push_back(AE_FMT_DTS);
if (CJNIAudioManager::GetSDKVersion() >= 21)
{
m_info.m_sampleRates.push_back(192000); // force support
m_info.m_dataFormats.push_back(AE_FMT_EAC3);
m_info.m_dataFormats.push_back(AE_FMT_DTSHD);
m_info.m_dataFormats.push_back(AE_FMT_TRUEHD);
}
}
#if 0 //defined(__ARM_NEON__)
if (g_cpuInfo.GetCPUFeatures() & CPU_FEATURE_NEON)
Expand Down

1 comment on commit cfe01c0

@stefaang
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just found this ... I'll check it tomorrow on my platform and provide feedback!

Please sign in to comment.