Commit 68e6fdd3 authored by Simon Morlat's avatar Simon Morlat
Browse files

sound improvements for Android

* workaround hardware bug on Galaxy S3 mini, where audio recorded is bad at 44100 and 32000 Hz.
* allow native sound system to work on Android 4.3 (few API changes)
* move android_echo.* files to a more generic implementation, so that it can be used on other OS as well.
parent 7c4bfcb0
......@@ -89,9 +89,9 @@ LOCAL_SRC_FILES = \
audiofilters/msg722.c \
audiofilters/l16.c \
audiofilters/msresample.c \
audiofilters/devices.c \
android/androidsound_depr.cpp \
android/loader.cpp \
android/android_echo.cpp \
android/androidsound.cpp \
android/AudioRecord.cpp \
android/AudioTrack.cpp \
......
......@@ -16,7 +16,6 @@ ANDROID_SRC_FILES= \
android/android-opengl-display.c \
android/audio.h \
android/loader.cpp android/loader.h \
android/android_echo.cpp android/android_echo.h \
audiofilters/webrtc_aec.c
EXTRA_DIST= audiofilters/winsnd2.c audiofilters/winsnd.c videofilters/winvideo.c \
......@@ -95,6 +94,7 @@ libmediastreamer_voip_la_SOURCES+= audiofilters/alaw.c \
audiofilters/chanadapt.c \
audiofilters/audiomixer.c \
audiofilters/tonedetector.c \
audiofilters/devices.c audiofilters/devices.h \
utils/g722.h \
utils/g722_decode.c \
utils/g722_encode.c \
......
......@@ -88,19 +88,46 @@ audio_io_handle_t AudioRecord::getInput() const{
return 0;
}
void AudioRecord::readBuffer(const void *p_info, Buffer *buffer){
if (AudioSystemImpl::get()->mApi18){
*buffer=*(const Buffer*)p_info;
}else{
const OldBuffer *oldbuf=(const OldBuffer*)p_info;
buffer->frameCount=oldbuf->frameCount;
buffer->size=oldbuf->size;
buffer->raw=oldbuf->raw;
}
}
bool AudioRecordImpl::init(Library *lib){
bool fail=false;
AudioRecordImpl *impl=new AudioRecordImpl(lib);
if (!impl->mCtorBeforeAPI17.isFound() && !impl->mCtor.isFound()) goto fail;
if (!impl->mDtor.isFound()) goto fail;
if (!impl->mInitCheck.isFound()) goto fail;
if (!impl->mStop.isFound()) goto fail;
if (!impl->mStart.isFound()) goto fail;
if (!impl->mCtorBeforeAPI17.isFound() && !impl->mCtor.isFound()) {
fail=true;
ms_error("AudioRecord::AudioRecord() not found.");
}
if (!impl->mDtor.isFound()) {
fail=true;
ms_error("AudioRecord::~AudioRecord() dtor not found.");
}
if (!impl->mInitCheck.isFound()) {
fail=true;
ms_error("AudioRecord::initCheck() not found.");
}
if (!impl->mStop.isFound()) {
fail=true;
ms_error("AudioRecord::stop() not found.");
}
if (!impl->mStart.isFound()) {
fail=true;
ms_error("AudioRecord::start() not found.");
}
if (fail){
delete impl;
return false;
}
sImpl=impl;
return true;
fail:
delete impl;
return false;
}
AudioRecordImpl *AudioRecordImpl::sImpl=NULL;
......
......@@ -52,7 +52,7 @@ public:
* and releaseBuffer().
*/
class Buffer
class OldBuffer
{
public:
enum {
......@@ -69,6 +69,23 @@ public:
int8_t* i8;
};
};
class Buffer //Android 4.3
{
public:
size_t frameCount; // number of sample frames corresponding to size;
// on input it is the number of frames desired,
// on output is the number of frames actually filled
size_t size; // input/output in byte units
union {
void* raw;
short* i16; // signed 16-bit
int8_t* i8; // unsigned 8-bit, offset by 0x80
};
};
static void readBuffer(const void *p_info, Buffer *buffer);
/* These are static methods to control the system-wide AudioFlinger
* only privileged processes can have access to them
......
......@@ -80,7 +80,7 @@ AudioSystemImpl::AudioSystemImpl(Library *lib) :
mSetPhoneState(lib, "_ZN7android11AudioSystem13setPhoneStateEi"),
mSetForceUse(lib, "_ZN7android11AudioSystem11setForceUseENS0_9force_useENS0_13forced_configE") {
//mGetInput(lib,"_ZN7android11AudioSystem8getInputEijjjNS0_18audio_in_acousticsE"){
mApi18=false;
// Try some Android 4.0 symbols if not found
if (!mSetForceUse.isFound()) {
mSetForceUse.load(lib, "_ZN7android11AudioSystem11setForceUseE24audio_policy_force_use_t25audio_policy_forced_cfg_t");
......@@ -89,9 +89,15 @@ AudioSystemImpl::AudioSystemImpl(Library *lib) :
// Then try some Android 4.1 symbols if still not found
if (!mGetOutputSamplingRate.isFound()) {
mGetOutputSamplingRate.load(lib, "_ZN7android11AudioSystem21getOutputSamplingRateEPi19audio_stream_type_t");
if (!mGetOutputSamplingRate.isFound()){
mGetOutputSamplingRate.load(lib,"_ZN7android11AudioSystem21getOutputSamplingRateEPj19audio_stream_type_t");
mApi18=true;
}
}
if (!mGetOutputFrameCount.isFound()) {
mGetOutputFrameCount.load(lib, "_ZN7android11AudioSystem19getOutputFrameCountEPi19audio_stream_type_t");
if (!mGetOutputFrameCount.isFound())
mGetOutputFrameCount.load(lib,"_ZN7android11AudioSystem19getOutputFrameCountEPj19audio_stream_type_t");
}
if (!mGetOutputLatency.isFound()) {
mGetOutputLatency.load(lib, "_ZN7android11AudioSystem16getOutputLatencyEPj19audio_stream_type_t");
......@@ -102,22 +108,42 @@ AudioSystemImpl::AudioSystemImpl(Library *lib) :
}
bool AudioSystemImpl::init(Library *lib){
bool fail=false;
AudioSystemImpl *impl=new AudioSystemImpl(lib);
if (!impl->mGetOutputSamplingRate.isFound()) goto fail;
if (!impl->mGetOutputFrameCount.isFound()) goto fail;
if (!impl->mGetOutputLatency.isFound()) goto fail;
if (!impl->mSetParameters.isFound()) goto fail;
if (!impl->mSetPhoneState.isFound()) goto fail;
if (!impl->mSetForceUse.isFound()) goto fail;
if (!impl->mGetOutputSamplingRate.isFound()){
ms_error("AudioSystem::getOutputSamplingRate() not found.");
fail=true;
}
if (!impl->mGetOutputFrameCount.isFound()) {
ms_error("AudioSystem::getOutputFrameCount() not found.");
fail=true;
}
if (!impl->mGetOutputLatency.isFound()){
ms_error("AudioSystem::getOutputLatency() not found.");
fail=true;
}
if (!impl->mSetParameters.isFound()) {
ms_error("AudioSystem::setParameters() not found.");
fail=true;
}
if (!impl->mSetPhoneState.isFound()){
ms_error("AudioSystem::setPhoneState() not found.");
fail=true;
}
if (!impl->mSetForceUse.isFound()) {
ms_error("AudioSystem::setForceUse() not found.");
fail=true;
}
//if (!impl->mGetInput.isFound()) goto fail;
sImpl=impl;
return true;
fail:
if (!fail){
sImpl=impl;
return true;
}else{
delete impl;
return false;
}
}
AudioSystemImpl *AudioSystemImpl::sImpl=NULL;
......
......@@ -244,6 +244,7 @@ private:
class AudioSystemImpl{
public:
bool mApi18;
static bool init(Library *lib);
static AudioSystemImpl *get(){
return sImpl;
......
......@@ -55,7 +55,9 @@ namespace fake_android{
}
status_t AudioTrack::initCheck()const{
return mImpl->mInitCheck.invoke(mThis);
if (mImpl->mInitCheck.isFound()) return mImpl->mInitCheck.invoke(mThis);
ms_warning("AudioTrack::initCheck() not available assuming OK");
return 0;
}
bool AudioTrack::stopped()const{
......@@ -100,13 +102,38 @@ namespace fake_android{
}
uint32_t AudioTrack::latency()const{
return mImpl->mLatency.invoke(mThis);
if (mImpl->mLatency.isFound())
return mImpl->mLatency.invoke(mThis);
else return (uint32_t)-1;
}
status_t AudioTrack::getPosition(uint32_t *frames){
return mImpl->mGetPosition.invoke(mThis,frames);
}
void AudioTrack::readBuffer(const void *p_info, Buffer *buffer){
if (AudioSystemImpl::get()->mApi18){
*buffer=*(const Buffer*)p_info;
}else{
const OldBuffer *oldbuf=(const OldBuffer*)p_info;
buffer->frameCount=oldbuf->frameCount;
buffer->size=oldbuf->size;
buffer->raw=oldbuf->raw;
}
}
void AudioTrack::writeBuffer(void *p_info, const Buffer *buffer){
if (AudioSystemImpl::get()->mApi18){
*(Buffer*)p_info=*buffer;
}else{
OldBuffer *oldbuf=(OldBuffer*)p_info;
oldbuf->frameCount=buffer->frameCount;
oldbuf->raw=buffer->raw;
oldbuf->size=buffer->size;
}
}
AudioTrackImpl::AudioTrackImpl(Library *lib) :
// By default, try to load Android 2.3 symbols
mCtor(lib,"_ZN7android10AudioTrackC1EijiiijPFviPvS1_ES1_ii"),
......@@ -123,6 +150,8 @@ namespace fake_android{
// Try some Android 2.2 symbols if not found
if (!mCtor.isFound()) {
mCtor.load(lib,"_ZN7android10AudioTrackC1EijiiijPFviPvS1_ES1_i");
if (!mCtor.isFound())
mCtor.load(lib,"_ZN7android10AudioTrackC1E19audio_stream_type_tj14audio_format_tji20audio_output_flags_tPFviPvS4_ES4_ii");
}
// Then try some Android 4.1 symbols if still not found
......@@ -132,20 +161,46 @@ namespace fake_android{
}
bool AudioTrackImpl::init(Library *lib){
bool fail=false;
AudioTrackImpl *impl=new AudioTrackImpl(lib);
if (!impl->mCtor.isFound()) goto fail;
if (!impl->mDtor.isFound()) goto fail;
if (!impl->mStart.isFound()) goto fail;
if (!impl->mStop.isFound()) goto fail;
if (!impl->mInitCheck.isFound()) goto fail;
if (!impl->mFlush.isFound()) goto fail;
if (!impl->mLatency.isFound()) goto fail;
if (!impl->mGetPosition.isFound()) goto fail;
sImpl=impl;
return true;
fail:
if (!impl->mCtor.isFound()) {
ms_error("AudioTrack::AudioTrack() not found");
fail=true;
}
if (!impl->mDtor.isFound()) {
ms_error("AudioTrack::~AudioTrack() not found");
fail=true;
}
if (!impl->mStart.isFound()) {
ms_error("AudioTrack::start() not found");
fail=true;
}
if (!impl->mStop.isFound()) {
ms_error("AudioTrack::stop() not found");
fail=true;
}
if (!impl->mInitCheck.isFound()) {
ms_warning("AudioTrack::initCheck() not found (normal in android 4.3)");
}
if (!impl->mFlush.isFound()) {
ms_error("AudioTrack::flush() not found");
fail=true;
}
if (!impl->mLatency.isFound()) {
ms_warning("AudioTrack::latency() not found (normal in android 4.3)");
}
if (!impl->mGetPosition.isFound()) {
ms_error("AudioTrack::getPosition() not found");
fail=true;
}
if (!fail){
sImpl=impl;
return true;
}else{
delete impl;
return false;
}
}
AudioTrackImpl * AudioTrackImpl::sImpl=NULL;
......
......@@ -53,7 +53,7 @@ public:
* and releaseBuffer(). See also callback_t for EVENT_MORE_DATA.
*/
class Buffer
class OldBuffer
{
public:
enum {
......@@ -75,8 +75,25 @@ public:
int8_t* i8; // unsigned 8-bit, offset by 0x80
};
};
class Buffer //Android 4.3
{
public:
size_t frameCount; // number of sample frames corresponding to size;
// on input it is the number of frames desired,
// on output is the number of frames actually filled
size_t size; // input/output in byte units
union {
void* raw;
short* i16; // signed 16-bit
int8_t* i8; // unsigned 8-bit, offset by 0x80
};
};
static void readBuffer(const void *p_info, Buffer *buffer);
static void writeBuffer(void *p_info, const Buffer *buffer);
/* As a convenience, if a callback is supplied, a handler thread
* is automatically created with the appropriate priority. This thread
* invokes the callback when a new buffer becomes available or various conditions occur.
......
......@@ -26,7 +26,7 @@
#include "AudioRecord.h"
#include "String8.h"
#include "android_echo.h"
#include "audiofilters/devices.h"
#define NATIVE_USE_HARDWARE_RATE 1
//#define TRACE_SND_WRITE_TIMINGS
......@@ -50,13 +50,17 @@ static int std_sample_rates[]={
};
struct AndroidNativeSndCardData{
AndroidNativeSndCardData(): mVoipMode(0) ,mIoHandle(0){
AndroidNativeSndCardData(int forced_rate=0): mVoipMode(0) ,mIoHandle(0){
/* try to use the same sampling rate as the playback.*/
int hwrate;
enableVoipMode();
if (AudioSystem::getOutputSamplingRate(&hwrate,AUDIO_STREAM_VOICE_CALL)==0){
ms_message("Hardware output sampling rate is %i",hwrate);
}
if (forced_rate){
ms_message("Hardware is known to have bugs at default sampling rate, using %i Hz instead.",forced_rate);
hwrate=forced_rate;
}
mPlayRate=mRecRate=hwrate;
for(int i=0;;){
int stdrate=std_sample_rates[i];
......@@ -137,8 +141,8 @@ struct AndroidSndReadData{
mCard=card;
#ifdef NATIVE_USE_HARDWARE_RATE
rate=card->mRecRate;
rec_buf_size=card->mRecFrames * 4;
#endif
rec_buf_size=card->mRecFrames * 4;
}
MSFilter *mFilter;
AndroidNativeSndCardData *mCard;
......@@ -256,19 +260,15 @@ MSSndCardDesc android_native_snd_card_desc={
static MSSndCard * android_snd_card_new(void)
{
MSSndCard * obj;
EchoCancellerParams params;
SoundDeviceDescription *d;
obj=ms_snd_card_new(&android_native_snd_card_desc);
obj->name=ms_strdup("android sound card");
obj->data=new AndroidNativeSndCardData();
if (android_sound_get_echo_params(&params)==0){
if (params.has_builtin_ec) obj->capabilities|=MS_SND_CARD_CAP_BUILTIN_ECHO_CANCELLER;
else obj->latency=params.delay;
}else{
obj->latency=0;
ms_warning("Model not echo-calibrated, will use default android latency value");
}
d=sound_device_description_get();
if (d->flags & DEVICE_HAS_BUILTIN_AEC) obj->capabilities|=MS_SND_CARD_CAP_BUILTIN_ECHO_CANCELLER;
obj->latency=d->delay;
obj->data=new AndroidNativeSndCardData(d->recommended_rate);
return obj;
}
......@@ -299,12 +299,13 @@ static void android_snd_read_cb(int event, void* user, void *p_info){
ms_ticker_set_time_func(obj->ticker,(uint64_t (*)(void*))ms_ticker_synchronizer_get_corrected_time, ad->mTickerSynchronizer);
}
if (event==AudioRecord::EVENT_MORE_DATA){
AudioRecord::Buffer * info=reinterpret_cast<AudioRecord::Buffer*>(p_info);
if (info->size > 0) {
mblk_t *m=allocb(info->size,0);
memcpy(m->b_wptr,info->raw,info->size);
m->b_wptr+=info->size;
ad->read_samples+=info->frameCount;
AudioRecord::Buffer info;
AudioRecord::readBuffer(p_info,&info);
if (info.size > 0) {
mblk_t *m=allocb(info.size,0);
memcpy(m->b_wptr,info.raw,info.size);
m->b_wptr+=info.size;
ad->read_samples+=info.frameCount;
ms_mutex_lock(&ad->mutex);
compute_timespec(ad);
......@@ -535,12 +536,14 @@ static void android_snd_write_cb(int event, void *user, void * p_info){
AndroidSndWriteData *ad=(AndroidSndWriteData*)user;
if (event==AudioTrack::EVENT_MORE_DATA){
AudioTrack::Buffer *info=reinterpret_cast<AudioTrack::Buffer *>(p_info);
AudioTrack::Buffer info;
int avail;
int ask;
AudioTrack::readBuffer(p_info,&info);
ms_mutex_lock(&ad->mutex);
ask = info->size;
ask = info.size;
avail = ms_bufferizer_get_avail(&ad->bf);
/* Drop the samples accumulated before the first callback asking for data. */
if ((ad->nbufs == 0) && (avail > (ask * 2))) {
......@@ -553,7 +556,8 @@ static void android_snd_write_cb(int event, void *user, void * p_info){
ad->minBufferFilling = avail;
}
}
info->size = MIN(avail, ask);
info.size = MIN(avail, ask);
info.frameCount = info.size / 2;
#ifdef TRACE_SND_WRITE_TIMINGS
{
MSTimeSpec ts;
......@@ -562,16 +566,15 @@ static void android_snd_write_cb(int event, void *user, void * p_info){
ask, info->size, avail, (avail / (2 * ad->nchannels)) / (ad->rate / 1000.0));
}
#endif
if (info->size > 0){
ms_bufferizer_read(&ad->bf,(uint8_t*)info->raw,info->size);
info->frameCount = info->size / 2;
if (info.size > 0){
ms_bufferizer_read(&ad->bf,(uint8_t*)info.raw,info.size);
}else{
/* we have an underrun (no more samples to deliver to the callback). We need to reset minBufferFilling*/
ad->minBufferFilling=-1;
}
ms_mutex_unlock(&ad->mutex);
ad->nbufs++;
ad->nFramesRequested+=info->frameCount;
ad->nFramesRequested+=info.frameCount;
/*
if (ad->nbufs %100){
uint32_t pos;
......@@ -580,6 +583,7 @@ static void android_snd_write_cb(int event, void *user, void * p_info){
}
}
*/
AudioTrack::writeBuffer(p_info,&info);
}else if (event==AudioTrack::EVENT_UNDERRUN){
ms_mutex_lock(&ad->mutex);
#ifdef TRACE_SND_WRITE_TIMINGS
......
......@@ -18,100 +18,112 @@
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <jni.h>
#include "android_echo.h"
#include "devices.h"
#ifdef ANDROID
#include "sys/system_properties.h"
#include <jni.h>
#endif
struct EcDescription{
const char *manufacturer;
const char *model;
const char *platform;
int has_builtin_ec;
int delay;
};
static EcDescription ec_table[]={
{ "HTC", "Nexus One", "qsd8k", FALSE, 300 },
{ "HTC", "HTC One X", "tegra", FALSE, 150 }, //has a very good acoustic isolation, which result in calibration saying no echo.
#ifdef ANDROID
static SoundDeviceDescription devices[]={
{ "HTC", "Nexus One", "qsd8k", 0, 300 },
{ "HTC", "HTC One X", "tegra", 0, 150 }, //has a very good acoustic isolation, which result in calibration saying no echo.
//But with speaker mode there is a strong echo if software ec is disabled.
{ "HTC", "HTC Desire", "", FALSE, 250 },
{ "HTC", "HTC Sensation Z710e", "", FALSE, 200 },
{ "HTC", "HTC Wildfire", "", FALSE, 270 },
{ "LGE", "Nexus 4", "msm8960", FALSE, 230 },
{ "LGE", "LS670", "", FALSE, 170 },
{ "motorola", "DROID RAZR", "", FALSE, 400 },
{ "motorola", "MB860", "", FALSE, 200 },
{ "motorola", "XT907", "", FALSE, 500 },
{ "samsung", "GT-S5360", "bcm21553", FALSE, 250 }, /*<Galaxy Y*/
{ "samsung", "GT-S5360L", "", FALSE, 250 }, /*<Galaxy Y*/
{ "samsung", "GT-S6102", "", TRUE, 0 }, /*<Galaxy Y duo*/
{ "samsung", "GT-S5570", "", FALSE, 160 }, /*<Galaxy Y duo*/
{ "samsung", "GT-S5300", "", TRUE, 0 }, /*<Galaxy Pocket*/
{ "samsung", "GT-S5830i", "", FALSE, 200 }, /* Galaxy S */
{ "samsung", "GT-S5830", "", FALSE, 170 }, /* Galaxy S */
{ "samsung", "GT-S5660", "", FALSE, 160 }, /* Galaxy Gio */
{ "samsung", "GT-I9000", "", FALSE, 200 }, /* Galaxy S */
{ "samsung", "GT-I9001", "", FALSE, 150 }, /* Galaxy S+ */
{ "samsung", "GT-I9070", "", TRUE, 0 }, /* Galaxy S Advance */
{ "samsung", "SPH-D700", "", FALSE, 200 }, /* Galaxy S Epic 4G*/
{ "samsung", "GT-I9100", "", TRUE, 0 }, /*Galaxy S2*/
{ "samsung", "GT-I9100P", "s5pc210", TRUE, 0 }, /*Galaxy S2*/
{ "samsung", "GT-S7562", "", TRUE, 0 }, /*<Galaxy S Duo*/
{ "samsung", "SCH-I415", "", TRUE, 0 }, /* Galaxy S ??*/
{ "samsung", "SCH-I425", "", TRUE, 0 }, /* Galaxy S ??*/
{ "samsung", "SCH-I535", "", TRUE, 0 }, /* Galaxy S ??*/
{ "samsung", "SPH-D710", "", TRUE, 0 }, /* Galaxy S2 Epic 4G*/
{ "samsung", "GT-I9300", "exynos4", TRUE, 0 }, /*Galaxy S3*/
{ "samsung", "SAMSUNG-SGH-I747","", TRUE, 0 }, /* Galaxy S3*/
{ "samsung", "SPH-L710","", TRUE, 0 }, /* Galaxy S3*/
{ "samsung", "SPH-D710","", TRUE, 0 }, /* Galaxy S3*/
{ "samsung", "SGH-T999", "", TRUE, 0 }, /*Galaxy S3*/
{ "samsung", "GT-I8190", "", TRUE, 0 }, /*Galaxy S3*/
{ "samsung", "SAMSUNG-SGH-I337","", TRUE, 0 }, /* Galaxy S4 ? */
{ "samsung", "GT-N7000", "", TRUE, 0 }, /*Galaxy Note*/
{ "samsung", "GT-N7100", "", TRUE, 0 }, /*Galaxy Note 2*/
{ "samsung", "GT-N7105", "", TRUE, 0 }, /*Galaxy Note 2*/
{ "samsung", "SGH-T889", "", TRUE, 0 }, /*Galaxy Note 2*/
{ "samsung", "Nexus S", "s5pc110", FALSE, 200 },
{ "samsung", "Galaxy Nexus", "", FALSE, 120 },
{ "samsung", "GT-S5570I", "", FALSE, 250},
{ "samsung", "GT-P3100", "", TRUE, 0 }, /* Galaxy Tab*/
{ "samsung", "GT-P7500", "", TRUE, 0 }, /* Galaxy Tab*/
{ "samsung", "GT-P7510", "", TRUE, 0 }, /* Galaxy Tab*/
{ "samsung", "GT-I915", "", TRUE, 0 }, /* Verizon Tab*/
{ "Sony Ericsson","ST15a", "", FALSE, 150 },
{ "Sony Ericsson","S51SE", "", FALSE, 150 },
{ "Sony Ericsson","SK17i", "", FALSE, 140 },
{ "Sony Ericsson","ST17i", "", FALSE, 130 },
{ "Sony Ericsson","ST18i", "", FALSE, 140 },
{ "Sony Ericsson","ST25i", "", FALSE, 320 },
{ "Sony Ericsson","ST27i", "", FALSE, 320 },
{ "Sony Ericsson","LT15i", "", FALSE, 150 },
{ "Sony Ericsson","LT18i", "", FALSE, 150 },
{ "Sony Ericsson","LT26i", "", FALSE, 230 },
{ "Sony Ericsson","LT26ii", "", FALSE, 230 },
{ "Sony Ericsson","LT28h", "", FALSE, 210 },
{ "Sony Ericsson","MT11i", "", FALSE, 150 },
{ "Sony Ericsson","MT15i", "", FALSE, 150 },
{ "Sony Ericsson","ST15i", "msm7x30", FALSE, 150 },
{ "asus", "Nexus 7", "", FALSE, 170 },
{ NULL, NULL, NULL, FALSE, 0}
{ "HTC", "HTC Desire", "", 0, 250 },
{ "HTC", "HTC Sensation Z710e", "", 0, 200 },
{ "HTC", "HTC Wildfire", "", 0, 270 },
{ "LGE", "Nexus 4", "msm8960", 0, 230 },
{ "LGE", "LS670", "", 0, 170 },
{ "motorola", "DROID RAZR", "", 0, 400 },
{ "motorola", "MB860", "", 0, 200 },
{ "motorola", "XT907", "", 0, 500 },
{ "samsung", "GT-S5360", "bcm21553", 0, 250 }, /*<Galaxy Y*/