Skip to content
Browse files

Merge pull request #5082 from anssih/android/pcm5.1

[android] Add 5.1 PCM playback support
  • Loading branch information...
2 parents 577bd0d + 0580934 commit 08cc0a788c0580a21cfce7fe9d9c0616519cde09 @jmarshallnz jmarshallnz committed Aug 11, 2014
View
27 xbmc/android/jni/AudioFormat.cpp
@@ -25,7 +25,21 @@
using namespace jni;
int CJNIAudioFormat::ENCODING_PCM_16BIT = 0x00000002;
-int CJNIAudioFormat::CHANNEL_OUT_STEREO = 0x0000000c;
+
+int CJNIAudioFormat::CHANNEL_OUT_STEREO = 0x0000000c;
+int CJNIAudioFormat::CHANNEL_OUT_5POINT1 = 0x000000fc;
+
+int CJNIAudioFormat::CHANNEL_OUT_FRONT_LEFT = 0x00000004;
+int CJNIAudioFormat::CHANNEL_OUT_FRONT_LEFT_OF_CENTER = 0x00000100;
+int CJNIAudioFormat::CHANNEL_OUT_FRONT_CENTER = 0x00000010;
+int CJNIAudioFormat::CHANNEL_OUT_FRONT_RIGHT_OF_CENTER = 0x00000200;
+int CJNIAudioFormat::CHANNEL_OUT_FRONT_RIGHT = 0x00000008;
+int CJNIAudioFormat::CHANNEL_OUT_LOW_FREQUENCY = 0x00000020;
+int CJNIAudioFormat::CHANNEL_OUT_BACK_LEFT = 0x00000040;
+int CJNIAudioFormat::CHANNEL_OUT_BACK_CENTER = 0x00000400;
+int CJNIAudioFormat::CHANNEL_OUT_BACK_RIGHT = 0x00000080;
+
+int CJNIAudioFormat::CHANNEL_INVALID = 0x00000000;
void CJNIAudioFormat::PopulateStaticFields()
{
@@ -36,6 +50,17 @@ void CJNIAudioFormat::PopulateStaticFields()
CJNIAudioFormat::ENCODING_PCM_16BIT = get_static_field<int>(c, "ENCODING_PCM_16BIT");
if (sdk >= 5)
CJNIAudioFormat::CHANNEL_OUT_STEREO = get_static_field<int>(c, "CHANNEL_OUT_STEREO");
+ CJNIAudioFormat::CHANNEL_OUT_5POINT1 = get_static_field<int>(c, "CHANNEL_OUT_5POINT1");
+ CJNIAudioFormat::CHANNEL_OUT_FRONT_LEFT = get_static_field<int>(c, "CHANNEL_OUT_FRONT_LEFT");
+ CJNIAudioFormat::CHANNEL_OUT_FRONT_LEFT_OF_CENTER = get_static_field<int>(c, "CHANNEL_OUT_FRONT_LEFT_OF_CENTER");
+ CJNIAudioFormat::CHANNEL_OUT_FRONT_CENTER = get_static_field<int>(c, "CHANNEL_OUT_FRONT_CENTER");
+ CJNIAudioFormat::CHANNEL_OUT_FRONT_RIGHT_OF_CENTER = get_static_field<int>(c, "CHANNEL_OUT_FRONT_RIGHT_OF_CENTER");
+ CJNIAudioFormat::CHANNEL_OUT_FRONT_RIGHT = get_static_field<int>(c, "CHANNEL_OUT_FRONT_RIGHT");
+ CJNIAudioFormat::CHANNEL_OUT_LOW_FREQUENCY = get_static_field<int>(c, "CHANNEL_OUT_LOW_FREQUENCY");
+ CJNIAudioFormat::CHANNEL_OUT_BACK_LEFT = get_static_field<int>(c, "CHANNEL_OUT_BACK_LEFT");
+ 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");
}
}
View
14 xbmc/android/jni/AudioFormat.h
@@ -28,7 +28,21 @@ class CJNIAudioFormat
static void PopulateStaticFields();
static int ENCODING_PCM_16BIT;
+
static int CHANNEL_OUT_STEREO;
+ static int CHANNEL_OUT_5POINT1;
+
+ static int CHANNEL_OUT_FRONT_LEFT;
+ static int CHANNEL_OUT_FRONT_LEFT_OF_CENTER;
+ static int CHANNEL_OUT_FRONT_CENTER;
+ static int CHANNEL_OUT_FRONT_RIGHT_OF_CENTER;
+ static int CHANNEL_OUT_FRONT_RIGHT;
+ static int CHANNEL_OUT_LOW_FREQUENCY;
+ static int CHANNEL_OUT_BACK_LEFT;
+ static int CHANNEL_OUT_BACK_CENTER;
+ static int CHANNEL_OUT_BACK_RIGHT;
+
+ static int CHANNEL_INVALID;
};
};
View
16 xbmc/android/jni/AudioTrack.cpp
@@ -37,12 +37,26 @@ void CJNIAudioTrack::PopulateStaticFields()
}
}
-CJNIAudioTrack::CJNIAudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode)
+CJNIAudioTrack::CJNIAudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode) throw(std::invalid_argument)
: CJNIBase("android/media/AudioTrack")
{
m_object = new_object(GetClassName(), "<init>", "(IIIIII)V",
streamType, sampleRateInHz, channelConfig,
audioFormat, bufferSizeInBytes, mode);
+
+ /* AudioTrack constructor may throw IllegalArgumentException, pass it to
+ * caller instead of getting us killed */
+ JNIEnv* jenv = xbmc_jnienv();
+ jthrowable exception = jenv->ExceptionOccurred();
+ if (exception)
+ {
+ jenv->ExceptionClear();
+ jhclass excClass = find_class(jenv, "java/lang/Throwable");
+ jmethodID toStrMethod = get_method_id(jenv, excClass, "toString", "()Ljava/lang/String;");
+ jhstring msg = call_method<jhstring>(exception, toStrMethod);
+ throw std::invalid_argument(jcast<std::string>(msg));
+ }
+
m_buffer = jharray(xbmc_jnienv()->NewByteArray(bufferSizeInBytes));
m_object.setGlobal();
View
4 xbmc/android/jni/AudioTrack.h
@@ -19,6 +19,8 @@
*
*/
+#include <stdexcept>
+
#include "JNIBase.h"
#include "ByteBuffer.h"
@@ -30,7 +32,7 @@ class CJNIAudioTrack : public CJNIBase
jharray m_buffer;
public:
- CJNIAudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode);
+ CJNIAudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode) throw(std::invalid_argument);
void play();
void stop();
View
170 xbmc/cores/AudioEngine/Sinks/AESinkAUDIOTRACK.cpp
@@ -61,6 +61,112 @@ static void pa_sconv_s16le_from_f32ne_neon(unsigned n, const float32_t *a, int16
}
#endif
+/*
+ * ADT-1 on L preview as of 2014-07 downmixes all non-5.1 content
+ * to stereo, so use 5.1 for all multichannel content for now to
+ * avoid that (except passthrough).
+ * If other devices surface that support other multichannel layouts,
+ * this should be disabled or adapted accordingly.
+ */
+#define LIMIT_TO_STEREO_AND_5POINT1 1
+
+static const AEChannel KnownChannels[] = { AE_CH_FL, AE_CH_FR, AE_CH_FC, AE_CH_LFE, AE_CH_BL, AE_CH_BR, AE_CH_BC, AE_CH_BLOC, AE_CH_BROC, AE_CH_NULL };
+
+static AEChannel AUDIOTRACKChannelToAEChannel(int atChannel)
+{
+ AEChannel aeChannel;
+
+ /* cannot use switch since CJNIAudioFormat is populated at runtime */
+
+ if (atChannel == CJNIAudioFormat::CHANNEL_OUT_FRONT_LEFT) aeChannel = AE_CH_FL;
+ else if (atChannel == CJNIAudioFormat::CHANNEL_OUT_FRONT_RIGHT) aeChannel = AE_CH_FR;
+ else if (atChannel == CJNIAudioFormat::CHANNEL_OUT_FRONT_CENTER) aeChannel = AE_CH_FC;
+ else if (atChannel == CJNIAudioFormat::CHANNEL_OUT_LOW_FREQUENCY) aeChannel = AE_CH_LFE;
+ else if (atChannel == CJNIAudioFormat::CHANNEL_OUT_BACK_LEFT) aeChannel = AE_CH_BL;
+ else if (atChannel == CJNIAudioFormat::CHANNEL_OUT_BACK_RIGHT) aeChannel = AE_CH_BR;
+ else if (atChannel == CJNIAudioFormat::CHANNEL_OUT_FRONT_LEFT_OF_CENTER) aeChannel = AE_CH_FLOC;
+ else if (atChannel == CJNIAudioFormat::CHANNEL_OUT_FRONT_RIGHT_OF_CENTER) aeChannel = AE_CH_FROC;
+ else if (atChannel == CJNIAudioFormat::CHANNEL_OUT_BACK_CENTER) aeChannel = AE_CH_BC;
+ else aeChannel = AE_CH_UNKNOWN1;
+
+ return aeChannel;
+}
+
+static int AEChannelToAUDIOTRACKChannel(AEChannel aeChannel)
+{
+ int atChannel;
+ switch (aeChannel)
+ {
+ case AE_CH_FL: atChannel = CJNIAudioFormat::CHANNEL_OUT_FRONT_LEFT; break;
+ case AE_CH_FR: atChannel = CJNIAudioFormat::CHANNEL_OUT_FRONT_RIGHT; break;
+ case AE_CH_FC: atChannel = CJNIAudioFormat::CHANNEL_OUT_FRONT_CENTER; break;
+ case AE_CH_LFE: atChannel = CJNIAudioFormat::CHANNEL_OUT_LOW_FREQUENCY; break;
+ case AE_CH_BL: atChannel = CJNIAudioFormat::CHANNEL_OUT_BACK_LEFT; break;
+ case AE_CH_BR: atChannel = CJNIAudioFormat::CHANNEL_OUT_BACK_RIGHT; break;
+ case AE_CH_BC: atChannel = CJNIAudioFormat::CHANNEL_OUT_BACK_CENTER; break;
+ case AE_CH_FLOC: atChannel = CJNIAudioFormat::CHANNEL_OUT_FRONT_LEFT_OF_CENTER; break;
+ case AE_CH_FROC: atChannel = CJNIAudioFormat::CHANNEL_OUT_FRONT_RIGHT_OF_CENTER; break;
+ default: atChannel = CJNIAudioFormat::CHANNEL_INVALID; break;
+ }
+ return atChannel;
+}
+
+static CAEChannelInfo AUDIOTRACKChannelMaskToAEChannelMap(int atMask)
+{
+ CAEChannelInfo info;
+
+ int mask = 0x1;
+ for (unsigned int i = 0; i < sizeof(int32_t) * 8; i++)
+ {
+ if (atMask & mask)
+ info += AUDIOTRACKChannelToAEChannel(mask);
+ mask <<= 1;
+ }
+
+ return info;
+}
+
+static int AEChannelMapToAUDIOTRACKChannelMask(CAEChannelInfo info)
+{
+#ifdef LIMIT_TO_STEREO_AND_5POINT1
+ if (info.Count() > 2 && info[0] != AE_CH_RAW)
+ return CJNIAudioFormat::CHANNEL_OUT_5POINT1;
+ else
+ return CJNIAudioFormat::CHANNEL_OUT_STEREO;
+#endif
+
+ info.ResolveChannels(KnownChannels);
+
+ int atMask = 0;
+
+ for (unsigned int i = 0; i < info.Count(); i++)
+ atMask |= AEChannelToAUDIOTRACKChannel(info[i]);
+
+ return atMask;
+}
+
+static jni::CJNIAudioTrack *CreateAudioTrack(int sampleRate, int channelMask, int bufferSize)
+{
+ jni::CJNIAudioTrack *jniAt = NULL;
+
+ try
+ {
+ jniAt = new CJNIAudioTrack(CJNIAudioManager::STREAM_MUSIC,
+ sampleRate,
+ channelMask,
+ CJNIAudioFormat::ENCODING_PCM_16BIT,
+ bufferSize,
+ CJNIAudioTrack::MODE_STREAM);
+ }
+ catch (const std::invalid_argument& e)
+ {
+ CLog::Log(LOGINFO, "AESinkAUDIOTRACK - AudioTrack creation (channelMask 0x%08x): %s", channelMask, e.what());
+ }
+
+ return jniAt;
+}
+
+
CAEDeviceInfo CAESinkAUDIOTRACK::m_info;
////////////////////////////////////////////////////////////////////////////////////////////
CAESinkAUDIOTRACK::CAESinkAUDIOTRACK()
@@ -94,24 +200,49 @@ bool CAESinkAUDIOTRACK::Initialize(AEAudioFormat &format, std::string &device)
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;
- m_format.m_channelLayout = m_info.m_channels;
- 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,
- CJNIAudioFormat::CHANNEL_OUT_STEREO,
- 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 = new CJNIAudioTrack( CJNIAudioManager::STREAM_MUSIC,
- m_format.m_sampleRate,
- CJNIAudioFormat::CHANNEL_OUT_STEREO,
- CJNIAudioFormat::ENCODING_PCM_16BIT,
- min_buffer_size,
- CJNIAudioTrack::MODE_STREAM);
+
+ 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,
+ 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,
+ min_buffer_size);
+
+ if (!m_at_jni)
+ {
+ if (atChannelMask != CJNIAudioFormat::CHANNEL_OUT_STEREO &&
+ atChannelMask != CJNIAudioFormat::CHANNEL_OUT_5POINT1)
+ {
+ atChannelMask = CJNIAudioFormat::CHANNEL_OUT_5POINT1;
+ CLog::Log(LOGDEBUG, "AESinkAUDIOTRACK - Retrying multichannel playback with a 5.1 layout");
+ }
+ else if (atChannelMask != CJNIAudioFormat::CHANNEL_OUT_STEREO)
+ {
+ atChannelMask = CJNIAudioFormat::CHANNEL_OUT_STEREO;
+ CLog::Log(LOGDEBUG, "AESinkAUDIOTRACK - Retrying with a stereo layout");
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "AESinkAUDIOTRACK - Unable to create AudioTrack");
+ return false;
+ }
+ }
+ }
+
m_format.m_frames = m_min_frames / 2;
m_format.m_frameSamples = m_format.m_frames * m_format.m_channelLayout.Count();
@@ -239,8 +370,11 @@ void CAESinkAUDIOTRACK::EnumerateDevicesEx(AEDeviceInfoList &list, bool force)
m_info.m_deviceName = "AudioTrack";
m_info.m_displayName = "android";
m_info.m_displayNameExtra = "audiotrack";
- m_info.m_channels += AE_CH_FL;
- m_info.m_channels += AE_CH_FR;
+#ifdef LIMIT_TO_STEREO_AND_5POINT1
+ m_info.m_channels = AE_CH_LAYOUT_5_1;
+#else
+ m_info.m_channels = KnownChannels;
+#endif
m_info.m_sampleRates.push_back(CJNIAudioTrack::getNativeOutputSampleRate(CJNIAudioManager::STREAM_MUSIC));
m_info.m_dataFormats.push_back(AE_FMT_S16LE);
m_info.m_dataFormats.push_back(AE_FMT_AC3);

0 comments on commit 08cc0a7

Please sign in to comment.
Something went wrong with that request. Please try again.