1. 程式人生 > >Android 音訊 OpenSL ES PCM資料播放

Android 音訊 OpenSL ES PCM資料播放

        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資料。