FM就是收音机,为了能让广大用户能够使用手机FM收听《中国之声》节目,小弟最近忙着移植FM的产线测试,但也遇到一点权限的小麻烦,记录一下,留作纪念。
FM的测试大致分为三部分:
1.FMTestService (FM服务)
2.FMTestActivity (FM测试主界面)
3.FMNative (jni调用系统的so库)
FMNative##
FMNative是用于调用系统的fm动态库的,系统fm的动态库是libfmjni.so
源码路径是/packages/apps/FMRadio/jni/fmr/libfm_jni.cpp
static const char *classPathNameRx = "com/android/fmradio/FmNative";
其对应的类名是com.android.fmradio.FmNative
所以我这个测试apk为了使用这个so库,必须新建一个com.android.fmradio.FmNative类,加载这个so库,并且声明库中的方法。
package com.android.fmradio;
/**
* This class define FM native interface, will description FM native interface
*/
import android.util.Log;
public class FmNative {
static {
Log.d("lee", "FMRadioNative before fmjni");
System.loadLibrary("fmjni");//加载so库
Log.d("lee", "FMRadioNative after fmjni");
}
//下面是声明so库中的方法
/**
* Open FM device, call before power up
*
* @return (true,success; false, failed)
*/
public static native boolean openDev();
/**
* Close FM device, call after power down
*
* @return (true, success; false, failed)
*/
public static native boolean closeDev();
/**
* power up FM with frequency use long antenna
*
* @param frequency frequency(50KHZ, 87.55; 100KHZ, 87.5)
*
* @return (true, success; false, failed)
*/
public static native boolean powerUp(float frequency);
/**
* Power down FM
*
* @param type (0, FMRadio; 1, FMTransimitter)
*
* @return (true, success; false, failed)
*/
public static native boolean powerDown(int type);
/**
* tune to frequency
*
* @param frequency frequency(50KHZ, 87.55; 100KHZ, 87.5)
*
* @return (true, success; false, failed)
*/
public static native boolean tune(float frequency);
/**
* seek with frequency in direction
*
* @param frequency frequency(50KHZ, 87.55; 100KHZ, 87.5)
* @param isUp (true, next station; false previous station)
*
* @return frequency(float)
*/
public static native float seek(float frequency, boolean isUp);
/**
* Auto scan(from 87.50-108.00)
*
* @return The scan station array(short)
*/
public static native short[] autoScan();
/**
* Stop scan, also can stop seek, other native when scan should call stop
* scan first, else will execute wait auto scan finish
*
* @return (true, can stop scan process; false, can't stop scan process)
*/
public static native boolean stopScan();
/**
* Open or close rds fuction
*
* @param rdson The rdson (true, open; false, close)
*
* @return rdsset
*/
public static native int setRds(boolean rdson);
/**
* Read rds events
*
* @return rds event type
*/
static native short readRds();
/**
* Get program service(program name)
*
* @return The program name
*/
public static native byte[] getPs();
/**
* Get radio text, RDS standard does not support Chinese character
*
* @return The LRT (Last Radio Text) bytes
*/
public static native byte[] getLrText();
/**
* Active alternative frequencies
*
* @return The frequency(float)
*/
public static native short activeAf();
/**
* Mute or unmute FM voice
*
* @param mute (true, mute; false, unmute)
*
* @return (true, success; false, failed)
*/
public static native int setMute(boolean mute);
/**
* Inquiry if RDS is support in driver
*
* @return (1, support; 0, NOT support; -1, error)
*/
public static native int isRdsSupport();
/**
* Switch antenna
*
* @param antenna antenna (0, long antenna, 1 short antenna)
*
* @return (0, success; 1 failed; 2 not support)
*/
public static native int switchAntenna(int antenna);
// FM EM start
/**
* get rssi from hardware(use for engineer mode)
*
* @return rssi value
*/
public static native int readRssi();
/**
* Inquiry if fm stereo mono(true, stereo; false mono)
*
* @return (true, stereo; false, mono)
*/
public static native boolean stereoMono();
/**
* Force set to stero/mono mode
*
* @param isMono
* (true, mono; false, stereo)
* @return (true, success; false, failed)
*/
public static native boolean setStereoMono(boolean isMono);
/**
* Read cap array of short antenna
*
* @return cap array value
*/
public static native short readCapArray();
/**
* read rds bler
*
* @return rds bler value
*/
public static native short readRdsBler();
/**
* send variables to native, and get some variables return.
* @param val send to native
* @return get value from native
*/
public static native short[] emcmd(short[] val);
/**
* set RSSI, desense RSSI, mute gain soft
* @param index flag which will execute
* (0:rssi threshold,1:desense rssi threshold,2: SGM threshold)
* @param value send to native
* @return execute ok or not
*/
public static native boolean emsetth(int index, int value);
/**
* get hardware version
*
* @return hardware version information array(0, ChipId; 1, EcoVersion; 2, PatchVersion; 3,
* DSPVersion)
*/
public static native int[] getHardwareVersion();
// FM EM end
}
FMTestService##
好了,通过上面FmNative,我们已经可以使用系统的fm so库了,接下来我们建立一个服务FMTestService,服务包含fm 上电,扫描等功能,其直接调用的是FmNative的函数。例如上电
public boolean powerUp(float frequency) {
...........
bRet = FmNative.powerUp(frequency);
Log.i(TAG, "<<< FMRadioService.powerUp: " + bRet);
.................
return bRet;
}
FMTestActivity##
好了,服务已经建好了,就等着被人调用了,好,我们的FM界面要登场了,那就是 FMTestActivity,他是fm测试的主界面,他启动FMTestService,调用FMTestService完成上电,扫描等功能。
但事情没有那么顺利,当一切完成后,我插上耳机,静静的等待,等待,没有声音,再等待,还是没有声音。。。
然后只能抓log分析罗,抓log发现打开fm设备失败
我们看看这个设备文件,我们可以看到这个文件的权限是media:media,660.而我的应用是system权限的,估计有问题,我们直接修改节点权限
chmod 777 fm
试下,发现还是没有声音,真蛋疼。
想起5.0后android加入了selinux(别问我怎么想起的,都是经验。。),这个也是权限相关的,或许跟这个有关系。于是我把selinux关掉试下
setenforce 0
满怀期待,再次进入fm,沙沙声传入了我的耳朵,FM通了!!终于通了!!我要去听《中国之声》了,再见。
总结:FM应用需要dev/fm的读写权限
FM应用需要操作FM文件夹的selinux权限
后记
Fm的两种播放方式
1.直连式
通过创建Audiopatch的方式播放,指定源和目标,可以让声音直连
int res = mAudioManager.createAudioPatch(audioPatchArray,
new AudioPortConfig[] {sourceConfig},
new AudioPortConfig[] {sinkConfig});
2.边录边播式
initAudioRecordSink(); //初始化播音录音者
createRenderThread(); //开启线程
private synchronized void initAudioRecordSink() {
Log.d(TAG, "initAudioRecordSink");
mAudioRecord = new AudioRecord(1998, /*MediaRecorder.AudioSource.FM_TUNER,*/
SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT, RECORD_BUF_SIZE);//录音源
mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_FORMAT, RECORD_BUF_SIZE, AudioTrack.MODE_STREAM); //播音源
}
//开启边录边播线程
private synchronized void createRenderThread() {
Log.d(TAG, "createRenderThread");
if (mRenderThread == null) {
mRenderThread = new RenderThread();
mRenderThread.start();
}
}