PCM 資料播放在開發中也經常使用,例如自己編寫播放器,解碼之後的音頻PCM資料,就可以通過OpenSL 播放,比用Java層的AudioTrack更快,延遲更低。
下面我們編寫OpenSL PCM播放,播放的主要邏輯是從檔案讀取PCM資料然後播放,代碼編寫環境Eclipse。
一、 Eclipse 建立Android工程
二、布局XML 建立檔案 /res/layout/activity_audio_track.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".URIActivity" >
<Button
android:id="@+id/btn_play"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="18dp"
android:text="播放" />
</LinearLayout>
布局檔案就一個播放按鈕
三、Activity類 建立/src/com/example/testopensl/AudioTrackActivity.java
package com.example.testopensl;
import com.example.audio.R;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
public class AudioTrackActivity extends Activity implements OnClickListener {
private static String URI_PCM = "/mnt/sdcard/pm.pcm";
static {
System.loadLibrary("TestAudio");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_audio_track);
findViewById(R.id.btn_play).setOnClickListener(this);
createEngine();
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_play:
createAudioPlayer(URI_PCM);
break;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
shutdown();
}
/** Native methods, implemented in jni folder */
public static native void createEngine();
public static native boolean createAudioPlayer(String uri);
public static native void setPlayingAudioPlayer(boolean isPlaying);
public static native void setVolumeAudioPlayer(int millibel);
public static native void setMutAudioPlayer(boolean mute);
public static native void shutdown();
}
四、編寫日志頭檔案,用于日志輸出, 建立/jni/log.h 檔案
#ifndef LOG_H_
#define LOG_H_
#include <android/log.h>
#ifndef DGB
#define DGB 0
#endif
#ifndef LOG_TAG
#define LOG_TAG __FILE__
#endif
#ifndef ALOGD
#if DGB
#define ALOGD(...) \
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#else
#define ALOGD(...) ((void)0)
#endif
#endif
#endif /* LOG_H_ */
五、用javah指令 生成jni 頭檔案. 檔案目錄/jni/com_example_testopensl_AudioTrackActivity.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_testopensl_AudioTrackActivity */
#ifndef _Included_com_example_testopensl_AudioTrackActivity
#define _Included_com_example_testopensl_AudioTrackActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_testopensl_AudioTrackActivity
* Method: createEngine
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_example_testopensl_AudioTrackActivity_createEngine
(JNIEnv *, jclass);
/*
* Class: com_example_testopensl_AudioTrackActivity
* Method: createAudioPlayer
* Signature: (Ljava/lang/String;)Z
*/
JNIEXPORT jboolean JNICALL Java_com_example_testopensl_AudioTrackActivity_createAudioPlayer
(JNIEnv *, jclass, jstring);
/*
* Class: com_example_testopensl_AudioTrackActivity
* Method: setPlayingAudioPlayer
* Signature: (Z)V
*/
JNIEXPORT void JNICALL Java_com_example_testopensl_AudioTrackActivity_setPlayingAudioPlayer
(JNIEnv *, jclass, jboolean);
/*
* Class: com_example_testopensl_AudioTrackActivity
* Method: setVolumeAudioPlayer
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_com_example_testopensl_AudioTrackActivity_setVolumeAudioPlayer
(JNIEnv *, jclass, jint);
/*
* Class: com_example_testopensl_AudioTrackActivity
* Method: setMutAudioPlayer
* Signature: (Z)V
*/
JNIEXPORT void JNICALL Java_com_example_testopensl_AudioTrackActivity_setMutAudioPlayer
(JNIEnv *, jclass, jboolean);
/*
* Class: com_example_testopensl_AudioTrackActivity
* Method: shutdown
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_example_testopensl_AudioTrackActivity_shutdown
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
六、JNI的實作 /jni/com_example_testopensl_AudioTrackActivity.cpp
/** log */
#define LOG_TAG "AudioTrackActivity"
#define DGB 1
#include "log.h"
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include <stdio.h>
#include <assert.h>
#include <pthread.h>
#include "com_example_testopensl_AudioTrackActivity.h"
class PlaybackThread;
// engine interfaces
static SLObjectItf engineObject = NULL;
static SLEngineItf engineEngine;
// output mix interfaces
static SLObjectItf outputMixObject = NULL;
// buffer queue player interfaces
static SLObjectItf bqPlayerObject = NULL;
static SLPlayItf bqPlayerPlay;
static SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue;
static SLEffectSendItf bqPlayerEffectSend;
static SLVolumeItf bqPlayerVolume;
static PlaybackThread* mThread;
class PlaybackThread {
private:
FILE *mFile;
void* mBuffer;
size_t mSize;
bool read;
public:
PlaybackThread(const char* uri) :
mFile(NULL), mBuffer(NULL), mSize(0), read(true) {
mFile = fopen((char*) uri, "r");
if (mFile == NULL) {
ALOGD("open file error %s", uri);
return;
}
mBuffer = malloc(8192);
}
void start() {
if (mFile == NULL) {
return;
}
enqueueBuffer();
}
// release file buffer
void release() {
if (mFile != NULL) {
fclose(mFile);
mFile == NULL;
}
if (mBuffer != NULL) {
free(mBuffer);
mBuffer == NULL;
}
}
~PlaybackThread() {
release();
ALOGD("~PlaybackThread");
}
void enqueueBuffer() {
if (bqPlayerBufferQueue == NULL) {
return;
}
// for streaming playback, replace this test by logic to find and fill the next buffer
while (true) {
if (read) {
mSize = fread(mBuffer, 1, 8192, mFile);
}
if (mSize > 0) {
SLresult result;
// enqueue another buffer
result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, mBuffer, mSize);
if (result == SL_RESULT_BUFFER_INSUFFICIENT) {
read = false;
return;
}
read = true;
} else {
return;
}
}
}
// this callback handler is called every time a buffer finishes playing
static void playerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) {
assert(NULL != context);
PlaybackThread* thread = (PlaybackThread *) context;
if (thread != NULL) {
thread->enqueueBuffer();
}
}
};
/*
* Class: com_example_testopensl_AudioTrackActivity
* Method: createEngine
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_example_testopensl_AudioTrackActivity_createEngine(JNIEnv *, jclass) {
SLresult result;
// create engine
result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
assert(SL_RESULT_SUCCESS == result);
(void) result;
// realize the engine
result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
assert(SL_RESULT_SUCCESS == result);
(void) result;
// get the engine interface, which is needed in order to create other objects
result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
assert(SL_RESULT_SUCCESS == result);
(void) result;
// create output mix,
result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 0, 0, 0);
assert(SL_RESULT_SUCCESS == result);
(void) result;
// realize the output mix
result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
assert(SL_RESULT_SUCCESS == result);
(void) result;
}
/*
* Class: com_example_testopensl_AudioTrackActivity
* Method: createAudioPlayer
* Signature: (Ljava/lang/String;)Z
*/
JNIEXPORT jboolean JNICALL Java_com_example_testopensl_AudioTrackActivity_createAudioPlayer(JNIEnv *env, jclass clazz, jstring uri) {
const char* utf8Uri = env->GetStringUTFChars(uri, NULL);
SLresult result;
// configure audio source
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 3 };
SLDataFormat_PCM format_pcm = { SL_DATAFORMAT_PCM, 2, SL_SAMPLINGRATE_44_1,
SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, SL_BYTEORDER_LITTLEENDIAN };
SLDataSource audioSrc = { &loc_bufq, &format_pcm };
// configure audio sink
SLDataLocator_OutputMix loc_outmix = { SL_DATALOCATOR_OUTPUTMIX, outputMixObject };
SLDataSink audioSnk = { &loc_outmix, NULL };
// create audio player
const SLInterfaceID ids[3] = { SL_IID_BUFFERQUEUE, SL_IID_EFFECTSEND, SL_IID_VOLUME };
const SLboolean req[3] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE };
result = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk, 3, ids, req);
assert(SL_RESULT_SUCCESS == result);
(void) result;
// realize the player
result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);
assert(SL_RESULT_SUCCESS == result);
(void) result;
// get the play interface
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay);
assert(SL_RESULT_SUCCESS == result);
(void) result;
// get the buffer queue interface
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, &bqPlayerBufferQueue);
assert(SL_RESULT_SUCCESS == result);
(void) result;
mThread = new PlaybackThread(utf8Uri);
// register callback on the buffer queue
result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, PlaybackThread::playerCallback, mThread);
assert(SL_RESULT_SUCCESS == result);
(void) result;
// get the effect send interface
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_EFFECTSEND, &bqPlayerEffectSend);
assert(SL_RESULT_SUCCESS == result);
(void) result;
// get the volume interface
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_VOLUME, &bqPlayerVolume);
assert(SL_RESULT_SUCCESS == result);
(void) result;
// set the player's state to playing
result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);
assert(SL_RESULT_SUCCESS == result);
(void) result;
//pthread_t id;
mThread->start();
env->ReleaseStringUTFChars(uri, utf8Uri);
ALOGD("createAudioPlayer finish");
return 0;
}
/*
* Class: com_example_testopensl_AudioTrackActivity
* Method: setPlayingAudioPlayer
* Signature: (Z)V
*/
JNIEXPORT void JNICALL Java_com_example_testopensl_AudioTrackActivity_setPlayingAudioPlayer(JNIEnv *, jclass, jboolean) {
}
/*
* Class: com_example_testopensl_AudioTrackActivity
* Method: setVolumeAudioPlayer
* Signature: (I)V
*/
JNIEXPORT void JNICALL Java_com_example_testopensl_AudioTrackActivity_setVolumeAudioPlayer(JNIEnv *, jclass, jint) {
}
/*
* Class: com_example_testopensl_AudioTrackActivity
* Method: setMutAudioPlayer
* Signature: (Z)V
*/
JNIEXPORT void JNICALL Java_com_example_testopensl_AudioTrackActivity_setMutAudioPlayer(JNIEnv *, jclass, jboolean) {
}
/*
* Class: com_example_testopensl_AudioTrackActivity
* Method: shutdown
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_example_testopensl_AudioTrackActivity_shutdown(JNIEnv *, jclass) {
//destory player object
if (bqPlayerObject != NULL) {
(*bqPlayerObject)->Destroy(bqPlayerObject);
bqPlayerPlay = NULL;
bqPlayerBufferQueue = NULL;
bqPlayerEffectSend = NULL;
bqPlayerVolume = NULL;
}
// destroy output mix object, and invalidate all associated interfaces
if (outputMixObject != NULL) {
(*outputMixObject)->Destroy(outputMixObject);
outputMixObject = NULL;
}
// destroy engine object, and invalidate all associated interfaces
if (engineObject != NULL) {
(*engineObject)->Destroy(engineObject);
engineObject = NULL;
engineEngine = NULL;
}
if (mThread != NULL) {
delete mThread;
mThread = NULL;
}
}
方法說明:
Java_com_example_testopensl_AudioTrackActivity_createEngine 建立引擎
Java_com_example_testopensl_AudioTrackActivity_createAudioPlayer 建立播放器并開始播放
Java_com_example_testopensl_AudioTrackActivity_shutdown 釋放資源
Java_com_example_testopensl_AudioTrackActivity_setPlayingAudioPlayer 未實作
Java_com_example_testopensl_AudioTrackActivity_setVolumeAudioPlayer 未實作
Java_com_example_testopensl_AudioTrackActivity_setMutAudioPlayer 未實作
未實作方法可以參考 URI播放實作
七、建立/jni/Android.mk 編譯檔案
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := TestAudio
LOCAL_SRC_FILES := com_example_testopensl_AudioTrackActivity.cpp
LOCAL_LDLIBS += -llog -lOpenSLES -landroid
include $(BUILD_SHARED_LIBRARY)
LOCAL_LDLIBS 需要加llog、lOpenSLES、landroid 編譯連結庫
八、AndroidManifest 配置
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.audio"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="9"
android:targetSdkVersion="19" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.example.testopensl.AudioTrackActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
編寫完Eclipse 結構圖如下:

運作之後界面圖如下:
代碼中使用的PCM資料資訊:
采樣率:44100
聲道:2
編碼:16BIT
關鍵代碼是在 com_example_testopensl_AudioTrackActivity.cpp 中建立播放器AudioSrc 參數
// configure audio source
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 3 };
SLDataFormat_PCM format_pcm = { SL_DATAFORMAT_PCM, 2, SL_SAMPLINGRATE_44_1,
SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, SL_BYTEORDER_LITTLEENDIAN };
SLDataSource audioSrc = { &loc_bufq, &format_pcm };
上面指定了播放PCM資料的資訊
代碼編寫完,功能比較簡單,點選播放按鈕從檔案讀取PCM資料。